When no accountId was provided, resolveTelegramAccount and resolveTelegramToken
resolved the implicit default account first. Any leftover TELEGRAM_BOT_TOKEN or
top-level channels.telegram.botToken then shadowed the configured
channels.telegram.defaultAccount and default-agent binding, so gateway sends,
heartbeats, and message actions went out through the wrong bot and typically
failed with chat not found in groups the stale bot never joined. Message
actions could also fail with a missing-token error when only the named default
account had a token.
Resolve the configured default account id up front in both optional-account
entry points, matching the nextcloud-talk resolver and createTelegramActionGate.
Review on #98102: the durable-retry classifier treated every MediaFetchError
`fetch_failed` as retryable, but `fetch_failed` also covers permanent failures
(SSRF/guard denials, local Bot API path/read errors). On spooled replay those
would requeue forever with the user-facing warning suppressed.
Move the policy to its owner: add `isDurablyRetryableMediaFetchError` to
`src/media/fetch.ts`, defined as `shouldRetryMediaFetch` (the canonical in-loop
transient policy) plus shutdown/abort `fetch_failed`. A restart-window abort is
the primary inbound-media loss vector and is recoverable on replay, whereas
in-loop retry mid-shutdown is futile; permanent `fetch_failed`, other 4xx, and
size limits stay non-retryable so they cannot loop in the spool.
Telegram reuses the shared helper instead of a local classifier
(`isAbortError`/`isTransientNetworkError` live in core; the classification must
not drift). Adds permanent-`fetch_failed` regression coverage at the unit and
behavioral level. Media-group partial delivery (#55216) unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A single-message inbound media fetch failure was handled best-effort: warn
and let the update complete, so bot-core acked the spooled update. During a
restart window the warning send also failed and the document was silently
lost with no durable retry.
Classify transient media fetch failures (network/abort fetch_failed and
408/429/5xx HTTP) and record them as failed-retryable, so spooled-replay
updates are kept and re-driven by the ingress spool with backoff instead of
acked. Live updates still ack and warn; permanent failures (size limit, 4xx)
stay best-effort. The retry notice is suppressed on spooled replay to avoid
repeating it on each replay.
The durable-retry classifier deliberately diverges from media/fetch.ts
shouldRetryMediaFetch on shutdown aborts: in-loop retry of a shutdown abort
is futile, but durable re-spool must retry it -- that abort is the primary
restart-window loss vector. Media-group partial delivery (#55216) is left
unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ClawSweeper review flagged that the new suppressReply test replaced the
existing direct-message binding regression test instead of being added
beside it. Restore the DM binding test to maintain coverage for the
plugin command dispatch surface.
Adds to the type so plugin
commands that handle their own transport delivery (e.g. via Telegram Bot API
directly with retry logic, IPv4 forcing, etc.) can signal the channel adapter
to skip the fallback reply.
When a plugin command handler returns , the Telegram
native command dispatcher now:
1. Cleans up any progress placeholder
2. Returns early without sending the "No response generated. Please try again." fallback
Includes detailed JSDoc for the new flag explaining its use
for plugin commands that deliver their own responses via channel-native APIs.
Fixes#80756
* test(telegram): add regression test for forum topic message_thread_id with streamed reasoning
After thorough code tracing of all delivery paths (draft stream, durable,
non-durable, preview hooks), the current main branch already correctly
passes message_thread_id through all paths for the streaming reasoning +
final answer scenario in forum topics. The bug reported in #89352 may have
been resolved by earlier merged changes (media-path preservation,
draft/progress streaming, etc.).
This commit adds a focused regression test covering the combined
scenario to prevent future regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(telegram): assert forum thread at draft-stream boundary for both lanes
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Preserve shared reasoning suppression by default while letting Telegram opt into durable reasoning payloads only when it has a deliverable reasoning lane. Covers persistent /reasoning on, separate reasoning stream lanes, and progress-stream suppression.\n\nVerification:\n- node scripts/run-vitest.mjs src/auto-reply/reply/dispatch-from-config.test.ts extensions/telegram/src/bot-message-dispatch.test.ts\n- git diff --check upstream/main...HEAD\n- .agents/skills/autoreview/scripts/autoreview --mode branch --base upstream/main --stream-engine-output\n- CI run 28411526182 green, including QA Smoke CI and check-test-types\n- Real behavior proof run 28411676681 passed\n\nPR: #97875
When Telegram rejects a rich message with RICH_MESSAGE_*_INVALID
(e.g., RICH_MESSAGE_EMAIL_INVALID on /status replies), the delivery
path now falls back to a plain text sendMessage instead of dropping
the reply.
Fixes#96363