@poolzin/pool-bot 2026.2.21 → 2026.2.22
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/CHANGELOG.md +17 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack native text streaming helpers.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Slack SDK's `ChatStreamer` (via `client.chatStream()`) to stream
|
|
5
|
+
* text responses word-by-word in a single updating message, matching Slack's
|
|
6
|
+
* "Agents & AI Apps" streaming UX.
|
|
7
|
+
*
|
|
8
|
+
* @see https://docs.slack.dev/ai/developing-ai-apps#streaming
|
|
9
|
+
* @see https://docs.slack.dev/reference/methods/chat.startStream
|
|
10
|
+
* @see https://docs.slack.dev/reference/methods/chat.appendStream
|
|
11
|
+
* @see https://docs.slack.dev/reference/methods/chat.stopStream
|
|
12
|
+
*/
|
|
13
|
+
import { logVerbose } from "../globals.js";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Stream lifecycle
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Start a new Slack text stream.
|
|
19
|
+
*
|
|
20
|
+
* Returns a {@link SlackStreamSession} that should be passed to
|
|
21
|
+
* {@link appendSlackStream} and {@link stopSlackStream}.
|
|
22
|
+
*
|
|
23
|
+
* The first chunk of text can optionally be included via `text`.
|
|
24
|
+
*/
|
|
25
|
+
export async function startSlackStream(params) {
|
|
26
|
+
const { client, channel, threadTs, text } = params;
|
|
27
|
+
logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}`);
|
|
28
|
+
const streamer = client.chatStream({
|
|
29
|
+
channel,
|
|
30
|
+
thread_ts: threadTs,
|
|
31
|
+
});
|
|
32
|
+
const session = {
|
|
33
|
+
streamer,
|
|
34
|
+
channel,
|
|
35
|
+
threadTs,
|
|
36
|
+
stopped: false,
|
|
37
|
+
};
|
|
38
|
+
// If initial text is provided, send it as the first append which will
|
|
39
|
+
// trigger the ChatStreamer to call chat.startStream under the hood.
|
|
40
|
+
if (text) {
|
|
41
|
+
await streamer.append({ markdown_text: text });
|
|
42
|
+
logVerbose(`slack-stream: appended initial text (${text.length} chars)`);
|
|
43
|
+
}
|
|
44
|
+
return session;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Append markdown text to an active Slack stream.
|
|
48
|
+
*/
|
|
49
|
+
export async function appendSlackStream(params) {
|
|
50
|
+
const { session, text } = params;
|
|
51
|
+
if (session.stopped) {
|
|
52
|
+
logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!text) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await session.streamer.append({ markdown_text: text });
|
|
59
|
+
logVerbose(`slack-stream: appended ${text.length} chars`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Stop (finalize) a Slack stream.
|
|
63
|
+
*
|
|
64
|
+
* After calling this the stream message becomes a normal Slack message.
|
|
65
|
+
* Optionally include final text to append before stopping.
|
|
66
|
+
*/
|
|
67
|
+
export async function stopSlackStream(params) {
|
|
68
|
+
const { session, text } = params;
|
|
69
|
+
if (session.stopped) {
|
|
70
|
+
logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
session.stopped = true;
|
|
74
|
+
logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
|
|
75
|
+
await session.streamer.stop(text ? { markdown_text: text } : undefined);
|
|
76
|
+
logVerbose("slack-stream: stream stopped");
|
|
77
|
+
}
|
|
@@ -3,18 +3,20 @@ import { listBoundAccountIds, resolveDefaultAgentBoundAccountId } from "../routi
|
|
|
3
3
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
|
4
4
|
import { resolveTelegramToken } from "./token.js";
|
|
5
5
|
const debugAccounts = (...args) => {
|
|
6
|
-
if (isTruthyEnvValue(process.env.POOLBOT_DEBUG_TELEGRAM_ACCOUNTS
|
|
6
|
+
if (isTruthyEnvValue(process.env.POOLBOT_DEBUG_TELEGRAM_ACCOUNTS)) {
|
|
7
7
|
console.warn("[telegram:accounts]", ...args);
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
function listConfiguredAccountIds(cfg) {
|
|
11
11
|
const accounts = cfg.channels?.telegram?.accounts;
|
|
12
|
-
if (!accounts || typeof accounts !== "object")
|
|
12
|
+
if (!accounts || typeof accounts !== "object") {
|
|
13
13
|
return [];
|
|
14
|
+
}
|
|
14
15
|
const ids = new Set();
|
|
15
16
|
for (const key of Object.keys(accounts)) {
|
|
16
|
-
if (!key)
|
|
17
|
+
if (!key) {
|
|
17
18
|
continue;
|
|
19
|
+
}
|
|
18
20
|
ids.add(normalizeAccountId(key));
|
|
19
21
|
}
|
|
20
22
|
return [...ids];
|
|
@@ -22,26 +24,31 @@ function listConfiguredAccountIds(cfg) {
|
|
|
22
24
|
export function listTelegramAccountIds(cfg) {
|
|
23
25
|
const ids = Array.from(new Set([...listConfiguredAccountIds(cfg), ...listBoundAccountIds(cfg, "telegram")]));
|
|
24
26
|
debugAccounts("listTelegramAccountIds", ids);
|
|
25
|
-
if (ids.length === 0)
|
|
27
|
+
if (ids.length === 0) {
|
|
26
28
|
return [DEFAULT_ACCOUNT_ID];
|
|
27
|
-
|
|
29
|
+
}
|
|
30
|
+
return ids.toSorted((a, b) => a.localeCompare(b));
|
|
28
31
|
}
|
|
29
32
|
export function resolveDefaultTelegramAccountId(cfg) {
|
|
30
33
|
const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram");
|
|
31
|
-
if (boundDefault)
|
|
34
|
+
if (boundDefault) {
|
|
32
35
|
return boundDefault;
|
|
36
|
+
}
|
|
33
37
|
const ids = listTelegramAccountIds(cfg);
|
|
34
|
-
if (ids.includes(DEFAULT_ACCOUNT_ID))
|
|
38
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
35
39
|
return DEFAULT_ACCOUNT_ID;
|
|
40
|
+
}
|
|
36
41
|
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
37
42
|
}
|
|
38
43
|
function resolveAccountConfig(cfg, accountId) {
|
|
39
44
|
const accounts = cfg.channels?.telegram?.accounts;
|
|
40
|
-
if (!accounts || typeof accounts !== "object")
|
|
45
|
+
if (!accounts || typeof accounts !== "object") {
|
|
41
46
|
return undefined;
|
|
47
|
+
}
|
|
42
48
|
const direct = accounts[accountId];
|
|
43
|
-
if (direct)
|
|
49
|
+
if (direct) {
|
|
44
50
|
return direct;
|
|
51
|
+
}
|
|
45
52
|
const normalized = normalizeAccountId(accountId);
|
|
46
53
|
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
|
|
47
54
|
return matchKey ? accounts[matchKey] : undefined;
|
|
@@ -52,6 +59,22 @@ function mergeTelegramAccountConfig(cfg, accountId) {
|
|
|
52
59
|
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
53
60
|
return { ...base, ...account };
|
|
54
61
|
}
|
|
62
|
+
export function createTelegramActionGate(params) {
|
|
63
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
64
|
+
const baseActions = params.cfg.channels?.telegram?.actions;
|
|
65
|
+
const accountActions = resolveAccountConfig(params.cfg, accountId)?.actions;
|
|
66
|
+
return (key, defaultValue = true) => {
|
|
67
|
+
const accountValue = accountActions?.[key];
|
|
68
|
+
if (accountValue !== undefined) {
|
|
69
|
+
return accountValue;
|
|
70
|
+
}
|
|
71
|
+
const baseValue = baseActions?.[key];
|
|
72
|
+
if (baseValue !== undefined) {
|
|
73
|
+
return baseValue;
|
|
74
|
+
}
|
|
75
|
+
return defaultValue;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
55
78
|
export function resolveTelegramAccount(params) {
|
|
56
79
|
const hasExplicitAccountId = Boolean(params.accountId?.trim());
|
|
57
80
|
const baseEnabled = params.cfg.channels?.telegram?.enabled !== false;
|
|
@@ -76,19 +99,23 @@ export function resolveTelegramAccount(params) {
|
|
|
76
99
|
};
|
|
77
100
|
const normalized = normalizeAccountId(params.accountId);
|
|
78
101
|
const primary = resolve(normalized);
|
|
79
|
-
if (hasExplicitAccountId)
|
|
102
|
+
if (hasExplicitAccountId) {
|
|
80
103
|
return primary;
|
|
81
|
-
|
|
104
|
+
}
|
|
105
|
+
if (primary.tokenSource !== "none") {
|
|
82
106
|
return primary;
|
|
107
|
+
}
|
|
83
108
|
// If accountId is omitted, prefer a configured account token over failing on
|
|
84
109
|
// the implicit "default" account. This keeps env-based setups working while
|
|
85
110
|
// making config-only tokens work for things like heartbeats.
|
|
86
111
|
const fallbackId = resolveDefaultTelegramAccountId(params.cfg);
|
|
87
|
-
if (fallbackId === primary.accountId)
|
|
112
|
+
if (fallbackId === primary.accountId) {
|
|
88
113
|
return primary;
|
|
114
|
+
}
|
|
89
115
|
const fallback = resolve(fallbackId);
|
|
90
|
-
if (fallback.tokenSource === "none")
|
|
116
|
+
if (fallback.tokenSource === "none") {
|
|
91
117
|
return primary;
|
|
118
|
+
}
|
|
92
119
|
return fallback;
|
|
93
120
|
}
|
|
94
121
|
export function listEnabledTelegramAccounts(cfg) {
|
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import { GrammyError, InputFile } from "grammy";
|
|
2
|
-
import { markdownToTelegramChunks, markdownToTelegramHtml, renderTelegramHtmlText, } from "../format.js";
|
|
3
|
-
import { withTelegramApiErrorLogging } from "../api-logging.js";
|
|
4
2
|
import { chunkMarkdownTextWithMode } from "../../auto-reply/chunk.js";
|
|
5
|
-
import {
|
|
6
|
-
import { danger, logVerbose } from "../../globals.js";
|
|
3
|
+
import { danger, logVerbose, warn } from "../../globals.js";
|
|
7
4
|
import { formatErrorMessage } from "../../infra/errors.js";
|
|
5
|
+
import { retryAsync } from "../../infra/retry.js";
|
|
8
6
|
import { mediaKindFromMime } from "../../media/constants.js";
|
|
9
7
|
import { fetchRemoteMedia } from "../../media/fetch.js";
|
|
10
8
|
import { isGifMedia } from "../../media/mime.js";
|
|
11
9
|
import { saveMediaBuffer } from "../../media/store.js";
|
|
12
10
|
import { loadWebMedia } from "../../web/media.js";
|
|
11
|
+
import { withTelegramApiErrorLogging } from "../api-logging.js";
|
|
12
|
+
import { splitTelegramCaption } from "../caption.js";
|
|
13
|
+
import { markdownToTelegramChunks, markdownToTelegramHtml, renderTelegramHtmlText, wrapFileReferencesInHtml, } from "../format.js";
|
|
13
14
|
import { buildInlineKeyboard } from "../send.js";
|
|
14
|
-
import { resolveTelegramVoiceSend } from "../voice.js";
|
|
15
|
-
import { buildTelegramThreadParams, resolveTelegramReplyId } from "./helpers.js";
|
|
16
15
|
import { cacheSticker, getCachedSticker } from "../sticker-cache.js";
|
|
16
|
+
import { resolveTelegramVoiceSend } from "../voice.js";
|
|
17
|
+
import { buildTelegramThreadParams, resolveTelegramMediaPlaceholder, resolveTelegramReplyId, } from "./helpers.js";
|
|
17
18
|
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
|
|
18
19
|
const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/;
|
|
20
|
+
const FILE_TOO_BIG_RE = /file is too big/i;
|
|
19
21
|
export async function deliverReplies(params) {
|
|
20
|
-
const { replies, chatId, runtime, bot, replyToMode, textLimit,
|
|
22
|
+
const { replies, chatId, runtime, bot, replyToMode, textLimit, thread, linkPreview, replyQuoteText, } = params;
|
|
21
23
|
const chunkMode = params.chunkMode ?? "length";
|
|
22
|
-
const threadParams = buildTelegramThreadParams(messageThreadId);
|
|
23
24
|
let hasReplied = false;
|
|
25
|
+
let hasDelivered = false;
|
|
26
|
+
const markDelivered = () => {
|
|
27
|
+
hasDelivered = true;
|
|
28
|
+
};
|
|
24
29
|
const chunkText = (markdown) => {
|
|
25
30
|
const markdownChunks = chunkMode === "newline"
|
|
26
31
|
? chunkMarkdownTextWithMode(markdown, textLimit, chunkMode)
|
|
@@ -30,7 +35,7 @@ export async function deliverReplies(params) {
|
|
|
30
35
|
const nested = markdownToTelegramChunks(chunk, textLimit, { tableMode: params.tableMode });
|
|
31
36
|
if (!nested.length && chunk) {
|
|
32
37
|
chunks.push({
|
|
33
|
-
html: markdownToTelegramHtml(chunk, { tableMode: params.tableMode }),
|
|
38
|
+
html: wrapFileReferencesInHtml(markdownToTelegramHtml(chunk, { tableMode: params.tableMode, wrapFileRefs: false })),
|
|
34
39
|
text: chunk,
|
|
35
40
|
});
|
|
36
41
|
continue;
|
|
@@ -50,6 +55,7 @@ export async function deliverReplies(params) {
|
|
|
50
55
|
continue;
|
|
51
56
|
}
|
|
52
57
|
const replyToId = replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId);
|
|
58
|
+
const replyToMessageIdForPayload = replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
|
|
53
59
|
const mediaList = reply.mediaUrls?.length
|
|
54
60
|
? reply.mediaUrls
|
|
55
61
|
: reply.mediaUrl
|
|
@@ -59,23 +65,28 @@ export async function deliverReplies(params) {
|
|
|
59
65
|
const replyMarkup = buildInlineKeyboard(telegramData?.buttons);
|
|
60
66
|
if (mediaList.length === 0) {
|
|
61
67
|
const chunks = chunkText(reply.text || "");
|
|
68
|
+
let sentTextChunk = false;
|
|
62
69
|
for (let i = 0; i < chunks.length; i += 1) {
|
|
63
70
|
const chunk = chunks[i];
|
|
64
|
-
if (!chunk)
|
|
71
|
+
if (!chunk) {
|
|
65
72
|
continue;
|
|
73
|
+
}
|
|
66
74
|
// Only attach buttons to the first chunk.
|
|
67
75
|
const shouldAttachButtons = i === 0 && replyMarkup;
|
|
68
76
|
await sendTelegramText(bot, chatId, chunk.html, runtime, {
|
|
69
|
-
replyToMessageId:
|
|
70
|
-
|
|
77
|
+
replyToMessageId: replyToMessageIdForPayload,
|
|
78
|
+
replyQuoteText,
|
|
79
|
+
thread,
|
|
71
80
|
textMode: "html",
|
|
72
81
|
plainText: chunk.text,
|
|
73
82
|
linkPreview,
|
|
74
83
|
replyMarkup: shouldAttachButtons ? replyMarkup : undefined,
|
|
75
84
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
sentTextChunk = true;
|
|
86
|
+
markDelivered();
|
|
87
|
+
}
|
|
88
|
+
if (replyToMessageIdForPayload && !hasReplied && sentTextChunk) {
|
|
89
|
+
hasReplied = true;
|
|
79
90
|
}
|
|
80
91
|
continue;
|
|
81
92
|
}
|
|
@@ -86,7 +97,9 @@ export async function deliverReplies(params) {
|
|
|
86
97
|
let pendingFollowUpText;
|
|
87
98
|
for (const mediaUrl of mediaList) {
|
|
88
99
|
const isFirstMedia = first;
|
|
89
|
-
const media = await loadWebMedia(mediaUrl
|
|
100
|
+
const media = await loadWebMedia(mediaUrl, {
|
|
101
|
+
localRoots: params.mediaLocalRoots,
|
|
102
|
+
});
|
|
90
103
|
const kind = mediaKindFromMime(media.contentType ?? undefined);
|
|
91
104
|
const isGif = isGifMedia({
|
|
92
105
|
contentType: media.contentType,
|
|
@@ -103,23 +116,24 @@ export async function deliverReplies(params) {
|
|
|
103
116
|
pendingFollowUpText = followUpText;
|
|
104
117
|
}
|
|
105
118
|
first = false;
|
|
106
|
-
const replyToMessageId =
|
|
119
|
+
const replyToMessageId = replyToMessageIdForPayload;
|
|
107
120
|
const shouldAttachButtonsToMedia = isFirstMedia && replyMarkup && !followUpText;
|
|
108
121
|
const mediaParams = {
|
|
109
122
|
caption: htmlCaption,
|
|
110
|
-
reply_to_message_id: replyToMessageId,
|
|
111
123
|
...(htmlCaption ? { parse_mode: "HTML" } : {}),
|
|
112
124
|
...(shouldAttachButtonsToMedia ? { reply_markup: replyMarkup } : {}),
|
|
125
|
+
...buildTelegramSendParams({
|
|
126
|
+
replyToMessageId,
|
|
127
|
+
thread,
|
|
128
|
+
}),
|
|
113
129
|
};
|
|
114
|
-
if (threadParams) {
|
|
115
|
-
mediaParams.message_thread_id = threadParams.message_thread_id;
|
|
116
|
-
}
|
|
117
130
|
if (isGif) {
|
|
118
131
|
await withTelegramApiErrorLogging({
|
|
119
132
|
operation: "sendAnimation",
|
|
120
133
|
runtime,
|
|
121
134
|
fn: () => bot.api.sendAnimation(chatId, file, { ...mediaParams }),
|
|
122
135
|
});
|
|
136
|
+
markDelivered();
|
|
123
137
|
}
|
|
124
138
|
else if (kind === "image") {
|
|
125
139
|
await withTelegramApiErrorLogging({
|
|
@@ -127,6 +141,7 @@ export async function deliverReplies(params) {
|
|
|
127
141
|
runtime,
|
|
128
142
|
fn: () => bot.api.sendPhoto(chatId, file, { ...mediaParams }),
|
|
129
143
|
});
|
|
144
|
+
markDelivered();
|
|
130
145
|
}
|
|
131
146
|
else if (kind === "video") {
|
|
132
147
|
await withTelegramApiErrorLogging({
|
|
@@ -134,6 +149,7 @@ export async function deliverReplies(params) {
|
|
|
134
149
|
runtime,
|
|
135
150
|
fn: () => bot.api.sendVideo(chatId, file, { ...mediaParams }),
|
|
136
151
|
});
|
|
152
|
+
markDelivered();
|
|
137
153
|
}
|
|
138
154
|
else if (kind === "audio") {
|
|
139
155
|
const { useVoice } = resolveTelegramVoiceSend({
|
|
@@ -153,6 +169,7 @@ export async function deliverReplies(params) {
|
|
|
153
169
|
shouldLog: (err) => !isVoiceMessagesForbidden(err),
|
|
154
170
|
fn: () => bot.api.sendVoice(chatId, file, { ...mediaParams }),
|
|
155
171
|
});
|
|
172
|
+
markDelivered();
|
|
156
173
|
}
|
|
157
174
|
catch (voiceErr) {
|
|
158
175
|
// Fall back to text if voice messages are forbidden in this chat.
|
|
@@ -164,19 +181,22 @@ export async function deliverReplies(params) {
|
|
|
164
181
|
throw voiceErr;
|
|
165
182
|
}
|
|
166
183
|
logVerbose("telegram sendVoice forbidden (recipient has voice messages blocked in privacy settings); falling back to text");
|
|
167
|
-
|
|
184
|
+
await sendTelegramVoiceFallbackText({
|
|
168
185
|
bot,
|
|
169
186
|
chatId,
|
|
170
187
|
runtime,
|
|
171
188
|
text: fallbackText,
|
|
172
189
|
chunkText,
|
|
173
|
-
replyToId,
|
|
174
|
-
|
|
175
|
-
hasReplied,
|
|
176
|
-
messageThreadId,
|
|
190
|
+
replyToId: replyToMessageIdForPayload,
|
|
191
|
+
thread,
|
|
177
192
|
linkPreview,
|
|
178
193
|
replyMarkup,
|
|
194
|
+
replyQuoteText,
|
|
179
195
|
});
|
|
196
|
+
if (replyToMessageIdForPayload && !hasReplied) {
|
|
197
|
+
hasReplied = true;
|
|
198
|
+
}
|
|
199
|
+
markDelivered();
|
|
180
200
|
// Skip this media item; continue with next.
|
|
181
201
|
continue;
|
|
182
202
|
}
|
|
@@ -190,6 +210,7 @@ export async function deliverReplies(params) {
|
|
|
190
210
|
runtime,
|
|
191
211
|
fn: () => bot.api.sendAudio(chatId, file, { ...mediaParams }),
|
|
192
212
|
});
|
|
213
|
+
markDelivered();
|
|
193
214
|
}
|
|
194
215
|
}
|
|
195
216
|
else {
|
|
@@ -198,6 +219,7 @@ export async function deliverReplies(params) {
|
|
|
198
219
|
runtime,
|
|
199
220
|
fn: () => bot.api.sendDocument(chatId, file, { ...mediaParams }),
|
|
200
221
|
});
|
|
222
|
+
markDelivered();
|
|
201
223
|
}
|
|
202
224
|
if (replyToId && !hasReplied) {
|
|
203
225
|
hasReplied = true;
|
|
@@ -208,26 +230,37 @@ export async function deliverReplies(params) {
|
|
|
208
230
|
const chunks = chunkText(pendingFollowUpText);
|
|
209
231
|
for (let i = 0; i < chunks.length; i += 1) {
|
|
210
232
|
const chunk = chunks[i];
|
|
211
|
-
const replyToMessageIdFollowup = replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
|
|
212
233
|
await sendTelegramText(bot, chatId, chunk.html, runtime, {
|
|
213
|
-
replyToMessageId:
|
|
214
|
-
|
|
234
|
+
replyToMessageId: replyToMessageIdForPayload,
|
|
235
|
+
thread,
|
|
215
236
|
textMode: "html",
|
|
216
237
|
plainText: chunk.text,
|
|
217
238
|
linkPreview,
|
|
218
239
|
replyMarkup: i === 0 ? replyMarkup : undefined,
|
|
219
240
|
});
|
|
220
|
-
|
|
221
|
-
hasReplied = true;
|
|
222
|
-
}
|
|
241
|
+
markDelivered();
|
|
223
242
|
}
|
|
224
243
|
pendingFollowUpText = undefined;
|
|
225
244
|
}
|
|
245
|
+
if (replyToMessageIdForPayload && !hasReplied) {
|
|
246
|
+
hasReplied = true;
|
|
247
|
+
}
|
|
226
248
|
}
|
|
227
249
|
}
|
|
250
|
+
return { delivered: hasDelivered };
|
|
228
251
|
}
|
|
229
252
|
export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
|
|
230
253
|
const msg = ctx.message;
|
|
254
|
+
const downloadAndSaveTelegramFile = async (filePath, fetchImpl) => {
|
|
255
|
+
const url = `https://api.telegram.org/file/bot${token}/${filePath}`;
|
|
256
|
+
const fetched = await fetchRemoteMedia({
|
|
257
|
+
url,
|
|
258
|
+
fetchImpl,
|
|
259
|
+
filePathHint: filePath,
|
|
260
|
+
});
|
|
261
|
+
const originalName = fetched.fileName ?? filePath;
|
|
262
|
+
return saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes, originalName);
|
|
263
|
+
};
|
|
231
264
|
// Handle stickers separately - only static stickers (WEBP) are supported
|
|
232
265
|
if (msg.sticker) {
|
|
233
266
|
const sticker = msg.sticker;
|
|
@@ -236,8 +269,9 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
|
|
|
236
269
|
logVerbose("telegram: skipping animated/video sticker (only static stickers supported)");
|
|
237
270
|
return null;
|
|
238
271
|
}
|
|
239
|
-
if (!sticker.file_id)
|
|
272
|
+
if (!sticker.file_id) {
|
|
240
273
|
return null;
|
|
274
|
+
}
|
|
241
275
|
try {
|
|
242
276
|
const file = await ctx.getFile();
|
|
243
277
|
if (!file.file_path) {
|
|
@@ -249,13 +283,7 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
|
|
|
249
283
|
logVerbose("telegram: fetch not available for sticker download");
|
|
250
284
|
return null;
|
|
251
285
|
}
|
|
252
|
-
const
|
|
253
|
-
const fetched = await fetchRemoteMedia({
|
|
254
|
-
url,
|
|
255
|
-
fetchImpl,
|
|
256
|
-
filePathHint: file.file_path,
|
|
257
|
-
});
|
|
258
|
-
const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes);
|
|
286
|
+
const saved = await downloadAndSaveTelegramFile(file.file_path, fetchImpl);
|
|
259
287
|
// Check sticker cache for existing description
|
|
260
288
|
const cached = sticker.file_unique_id ? getCachedSticker(sticker.file_unique_id) : null;
|
|
261
289
|
if (cached) {
|
|
@@ -303,10 +331,38 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
|
|
|
303
331
|
return null;
|
|
304
332
|
}
|
|
305
333
|
}
|
|
306
|
-
const m = msg.photo?.[msg.photo.length - 1] ??
|
|
307
|
-
|
|
334
|
+
const m = msg.photo?.[msg.photo.length - 1] ??
|
|
335
|
+
msg.video ??
|
|
336
|
+
msg.video_note ??
|
|
337
|
+
msg.document ??
|
|
338
|
+
msg.audio ??
|
|
339
|
+
msg.voice;
|
|
340
|
+
if (!m?.file_id) {
|
|
308
341
|
return null;
|
|
309
|
-
|
|
342
|
+
}
|
|
343
|
+
let file;
|
|
344
|
+
try {
|
|
345
|
+
file = await retryAsync(() => ctx.getFile(), {
|
|
346
|
+
attempts: 3,
|
|
347
|
+
minDelayMs: 1000,
|
|
348
|
+
maxDelayMs: 4000,
|
|
349
|
+
jitter: 0.2,
|
|
350
|
+
label: "telegram:getFile",
|
|
351
|
+
shouldRetry: isRetryableGetFileError,
|
|
352
|
+
onRetry: ({ attempt, maxAttempts }) => logVerbose(`telegram: getFile retry ${attempt}/${maxAttempts}`),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
// Handle "file is too big" separately - Telegram Bot API has a 20MB download limit
|
|
357
|
+
if (isFileTooBigError(err)) {
|
|
358
|
+
logVerbose(warn("telegram: getFile failed - file exceeds Telegram Bot API 20MB limit; skipping attachment"));
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
// All retries exhausted — return null so the message still reaches the agent
|
|
362
|
+
// with a type-based placeholder (e.g. <media:audio>) instead of being dropped.
|
|
363
|
+
logVerbose(`telegram: getFile failed after retries: ${String(err)}`);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
310
366
|
if (!file.file_path) {
|
|
311
367
|
throw new Error("Telegram getFile returned no file_path");
|
|
312
368
|
}
|
|
@@ -314,20 +370,8 @@ export async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
|
|
|
314
370
|
if (!fetchImpl) {
|
|
315
371
|
throw new Error("fetch is not available; set channels.telegram.proxy in config");
|
|
316
372
|
}
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
url,
|
|
320
|
-
fetchImpl,
|
|
321
|
-
filePathHint: file.file_path,
|
|
322
|
-
});
|
|
323
|
-
const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes);
|
|
324
|
-
let placeholder = "<media:document>";
|
|
325
|
-
if (msg.photo)
|
|
326
|
-
placeholder = "<media:image>";
|
|
327
|
-
else if (msg.video)
|
|
328
|
-
placeholder = "<media:video>";
|
|
329
|
-
else if (msg.audio || msg.voice)
|
|
330
|
-
placeholder = "<media:audio>";
|
|
373
|
+
const saved = await downloadAndSaveTelegramFile(file.file_path, fetchImpl);
|
|
374
|
+
const placeholder = resolveTelegramMediaPlaceholder(msg) ?? "<media:document>";
|
|
331
375
|
return { path: saved.path, contentType: saved.contentType, placeholder };
|
|
332
376
|
}
|
|
333
377
|
function isVoiceMessagesForbidden(err) {
|
|
@@ -336,27 +380,46 @@ function isVoiceMessagesForbidden(err) {
|
|
|
336
380
|
}
|
|
337
381
|
return VOICE_FORBIDDEN_RE.test(formatErrorMessage(err));
|
|
338
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Returns true if the error is Telegram's "file is too big" error.
|
|
385
|
+
* This happens when trying to download files >20MB via the Bot API.
|
|
386
|
+
* Unlike network errors, this is a permanent error and should not be retried.
|
|
387
|
+
*/
|
|
388
|
+
function isFileTooBigError(err) {
|
|
389
|
+
if (err instanceof GrammyError) {
|
|
390
|
+
return FILE_TOO_BIG_RE.test(err.description);
|
|
391
|
+
}
|
|
392
|
+
return FILE_TOO_BIG_RE.test(formatErrorMessage(err));
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Returns true if the error is a transient network error that should be retried.
|
|
396
|
+
* Returns false for permanent errors like "file is too big" (400 Bad Request).
|
|
397
|
+
*/
|
|
398
|
+
function isRetryableGetFileError(err) {
|
|
399
|
+
// Don't retry "file is too big" - it's a permanent 400 error
|
|
400
|
+
if (isFileTooBigError(err)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
// Retry all other errors (network issues, timeouts, etc.)
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
339
406
|
async function sendTelegramVoiceFallbackText(opts) {
|
|
340
407
|
const chunks = opts.chunkText(opts.text);
|
|
341
|
-
let hasReplied = opts.hasReplied;
|
|
342
408
|
for (let i = 0; i < chunks.length; i += 1) {
|
|
343
409
|
const chunk = chunks[i];
|
|
344
410
|
await sendTelegramText(opts.bot, opts.chatId, chunk.html, opts.runtime, {
|
|
345
|
-
replyToMessageId: opts.replyToId
|
|
346
|
-
|
|
411
|
+
replyToMessageId: opts.replyToId,
|
|
412
|
+
replyQuoteText: opts.replyQuoteText,
|
|
413
|
+
thread: opts.thread,
|
|
347
414
|
textMode: "html",
|
|
348
415
|
plainText: chunk.text,
|
|
349
416
|
linkPreview: opts.linkPreview,
|
|
350
417
|
replyMarkup: i === 0 ? opts.replyMarkup : undefined,
|
|
351
418
|
});
|
|
352
|
-
if (opts.replyToId && !hasReplied) {
|
|
353
|
-
hasReplied = true;
|
|
354
|
-
}
|
|
355
419
|
}
|
|
356
|
-
return hasReplied;
|
|
357
420
|
}
|
|
358
421
|
function buildTelegramSendParams(opts) {
|
|
359
|
-
const threadParams = buildTelegramThreadParams(opts?.
|
|
422
|
+
const threadParams = buildTelegramThreadParams(opts?.thread);
|
|
360
423
|
const params = {};
|
|
361
424
|
if (opts?.replyToMessageId) {
|
|
362
425
|
params.reply_to_message_id = opts.replyToMessageId;
|
|
@@ -369,7 +432,7 @@ function buildTelegramSendParams(opts) {
|
|
|
369
432
|
async function sendTelegramText(bot, chatId, text, runtime, opts) {
|
|
370
433
|
const baseParams = buildTelegramSendParams({
|
|
371
434
|
replyToMessageId: opts?.replyToMessageId,
|
|
372
|
-
|
|
435
|
+
thread: opts?.thread,
|
|
373
436
|
});
|
|
374
437
|
// Add link_preview_options when link preview is disabled.
|
|
375
438
|
const linkPreviewEnabled = opts?.linkPreview ?? true;
|