Skip to content

fix: forward forceDocument through sendPayload path (follow-up to #45111)#46119

Closed
thepagent wants to merge 68 commits intoopenclaw:mainfrom
thepagent:fix/force-document-sendpayload-path
Closed

fix: forward forceDocument through sendPayload path (follow-up to #45111)#46119
thepagent wants to merge 68 commits intoopenclaw:mainfrom
thepagent:fix/force-document-sendpayload-path

Conversation

@thepagent
Copy link
Copy Markdown
Contributor

Follow-up to #45111.

Problem

--force-document was silently ignored whenever a payload carried Telegram channelData (e.g. inline-button payloads). Two links in the chain were missing:

  1. deliverOutboundPayloadsCore built sendOverrides without forceDocument, so it was never passed to handler.sendPayload.
  2. telegramOutbound.sendPayload did not accept forceDocument in its destructured params, and did not forward it to sendTelegramPayloadMessages via baseOpts.

The sendMedia path was already correct (fixed in #45111); only the sendPayload path was affected.

Fix

┌─────────────────────────────────────────┐
│  deliverOutboundPayloadsCore            │
│  sendOverrides = {                      │
│    replyToId, threadId,                 │
│    forceDocument  ← added               │
│  }                                      │
└────────────────┬────────────────────────┘
                 │ handler.sendPayload(payload, sendOverrides)
                 ▼
┌─────────────────────────────────────────┐
│  telegramOutbound.sendPayload           │
│  { ..., forceDocument }  ← destructured │
│  baseOpts: {                            │
│    ...baseOpts, mediaLocalRoots,        │
│    forceDocument  ← forwarded           │
│  }                                      │
└─────────────────────────────────────────┘

Changes

  • src/infra/outbound/deliver.ts — add forceDocument to sendOverrides
  • extensions/telegram/src/outbound-adapter.ts — accept and forward forceDocument in sendPayload

sendPayload in telegramOutbound did not accept or forward forceDocument,
so --force-document was silently ignored for payloads with channelData
(e.g. inline-button payloads). Also, deliverOutboundPayloadsCore did not
include forceDocument in sendOverrides passed to handler.sendPayload.

Fixes the two missing links:
- extensions/telegram/src/outbound-adapter.ts: accept forceDocument in
  sendPayload and pass it through to sendTelegramPayloadMessages baseOpts
- src/infra/outbound/deliver.ts: include forceDocument in sendOverrides

Reported as follow-up to openclaw#45111.
@openclaw-barnacle openclaw-barnacle bot added channel: telegram Channel integration: telegram size: XS labels Mar 14, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR correctly fixes a two-link chain that caused --force-document to be silently ignored when a payload included Telegram channelData. The deliver.ts change ensures forceDocument is included in sendOverrides before dispatching to handler.sendPayload, and the outbound-adapter.ts change ensures sendPayload accepts and threads forceDocument through to sendTelegramPayloadMessages via baseOpts.

Key observations:

  • The fix is surgical and consistent with the existing sendMedia path in outbound-adapter.ts, which already used forceDocument ?? false.
  • channel.ts correctly adds forceDocument to both buildTelegramSendOptions's parameter type and its return value, keeping the plugin-level and adapter-level implementations in sync.
  • The new regression test in channel.test.ts directly validates that forceDocument: true flows through telegramPlugin.outbound.sendPayload to the underlying send call when channelData is present, closing the gap noted in the previous review thread.

Confidence Score: 5/5

  • This PR is safe to merge — the fix is narrowly scoped, consistent with the existing pattern in the sendMedia path, and is backed by a new regression test.
  • Both fix sites are straightforward one-liner additions that mirror the already-correct sendMedia path. The forceDocument ?? false default in outbound-adapter.ts is consistent with existing behaviour. The new test directly exercises the previously-broken sendPayload + channelData + forceDocument combination. No other callsites or data flows appear to be affected.
  • No files require special attention.

Last reviewed commit: 64eb2c3

Comment on lines 144 to +160
@@ -156,6 +157,7 @@ export const telegramOutbound: ChannelOutboundAdapter = {
baseOpts: {
...baseOpts,
mediaLocalRoots,
forceDocument: forceDocument ?? false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test coverage for the fixed path

The existing sendPayload tests in channel.test.ts (lines 294 and 327) don't pass or assert forceDocument, so the regression this PR fixes has no test to prevent it from silently breaking again. Consider adding a test along the lines of the existing forceDocument test in send.test.ts (line 895) but targeting sendPayload with channelData present — verifying that forceDocument: true in the context is forwarded to the underlying send call.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/telegram/src/outbound-adapter.ts
Line: 144-160

Comment:
**No test coverage for the fixed path**

The existing `sendPayload` tests in `channel.test.ts` (lines 294 and 327) don't pass or assert `forceDocument`, so the regression this PR fixes has no test to prevent it from silently breaking again. Consider adding a test along the lines of the existing `forceDocument` test in `send.test.ts` (line 895) but targeting `sendPayload` with `channelData` present — verifying that `forceDocument: true` in the context is forwarded to the underlying send call.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in c1f0de8.

New describe block in channel.test.ts calls sendPayload with channelData present and forceDocument: true, then asserts the underlying send receives forceDocument: true. This directly guards the regression.

@thepagent
Copy link
Copy Markdown
Contributor Author

@codex @greptile-apps please re-review, test coverage has been added in c1f0de8.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1f0de8d31

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 157 to +160
baseOpts: {
...baseOpts,
mediaLocalRoots,
forceDocument: forceDocument ?? false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Patch Telegram plugin path that is actually registered

This change updates extensions/telegram/src/outbound-adapter.ts, but bundled runtime registration still wires Telegram via extensions/telegram/index.ts to telegramPlugin in extensions/telegram/src/channel.ts, and outbound delivery resolves plugin.outbound from that registry entry (src/channels/plugins/outbound/load.ts). Because telegramPlugin.outbound.sendPayload still builds send options without forceDocument, the normal channels.telegram delivery path continues to ignore --force-document for payloads with channelData, so the intended fix is not applied in production.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed in 64eb2c3channel.ts is the production path; added forceDocument to buildTelegramSendOptions and destructured it in sendPayload.

thepagent and others added 21 commits March 14, 2026 13:09
The telegramPlugin.outbound.sendPayload in channel.ts is the actual
production path used by the plugin registry. It was not forwarding
forceDocument, making the fix in outbound-adapter.ts ineffective.

- Add forceDocument to buildTelegramSendOptions params and output
- Destructure and pass forceDocument in sendPayload

Addresses Codex review on openclaw#46119.
* ci: move docker release to GitHub-hosted runners

* ci: annotate docker release runner guardrails
* feat(agent): add /btw side questions

* fix(agent): gate and log /btw reviews

* feat(btw): isolate side-question delivery

* test(reply): update route reply runtime mocks

* fix(btw): complete side-result delivery across clients

* fix(gateway): handle streamed btw side results

* fix(telegram): unblock btw side questions

* fix(reply): make external btw replies explicit

* fix(chat): keep btw side results ephemeral in internal history

* fix(btw): address remaining review feedback

* fix(chat): preserve btw history on mobile refresh

* fix(acp): keep btw replies out of prompt history

* refactor(btw): narrow side questions to live channels

* fix(btw): preserve channel typing indicators

* fix(btw): keep side questions isolated in chat

* fix(outbound): restore typed channel send deps

* fix(btw): avoid blocking replies on transcript persistence

* fix(btw): keep side questions fast

* docs(commands): document btw slash command

* docs(changelog): add btw side questions entry

* test(outbound): align session transcript mocks
* ci: add docker release backfill workflow

* ci: add manual backfill support to docker release

* ci: keep docker latest tags off manual backfills
…w#46301)

* fix: avoid configure startup plugin stalls

* fix: credit configure startup changelog entry
* fix(btw): stop persisting side questions

* docs(btw): document side-question behavior
* Slack: forward reply blocks in DM delivery

* Slack: preserve reply blocks in preview finalization

* Slack: cover block-only DM replies

* Changelog: note Slack interactive reply fix
* Docs: document Telegram force-document sends

* Docs: note Telegram document send behavior

* Docs: clarify memory file precedence

* Docs: align default AGENTS memory guidance

* Docs: update workspace FAQ memory note

* Docs: document gateway status require-rpc

* Docs: add require-rpc to gateway CLI index
Fixes openclaw#43322

* fix(feishu): clear stale streamingStartPromise on card creation failure

When FeishuStreamingSession.start() throws (HTTP 400), the catch block
sets streaming = null but leaves streamingStartPromise dangling. The
guard in startStreaming() checks streamingStartPromise first, so all
future deliver() calls silently skip streaming - the session locks
permanently.

Clear streamingStartPromise in the catch block so subsequent messages
can retry streaming instead of dropping all future replies.

Fixes openclaw#43322

* test(feishu): wrap push override in try/finally for cleanup safety
Fixes openclaw#42931

When gateway.auth.mode is set to "none", authentication succeeds with
method "none" but sharedAuthOk remains false because the auth-context
only recognises token/password/trusted-proxy methods. This causes all
pairing-skip conditions to fail, so Control UI browser connections get
closed with code 1008 "pairing required" despite auth being disabled.

Short-circuit the skipPairing check: if the operator explicitly
disabled authentication, device pairing (which is itself an auth
mechanism) must also be bypassed.

Fixes openclaw#42931
Fixes openclaw#43057

* fix(auth): clear stale lockout on re-login

Clear stale `auth_permanent` and `billing` disabled state for all
profiles matching the target provider when `openclaw models auth login`
is invoked, so users locked out by expired or revoked OAuth tokens can
recover by re-authenticating instead of waiting for the cooldown timer.

Uses the agent-scoped store (`loadAuthProfileStoreForRuntime`) for
correct multi-agent profile resolution and wraps the housekeeping in
try/catch so corrupt store files never block re-authentication.

Fixes openclaw#43057

* test(auth): remove unnecessary non-null assertions

oxlint no-unnecessary-type-assertion: invocationCallOrder[0]
already returns number, not number | undefined.
…nclaw#46066)

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: scoootscooob <[email protected]>
Co-authored-by: Tak Hoffman <[email protected]>
… providers

Fixes openclaw#46142

Stop forcing supportsUsageInStreaming=false on non-native openai-completions
endpoints. Most OpenAI-compatible APIs (DashScope, DeepSeek, Groq, Together,
etc.) handle stream_options: { include_usage: true } correctly. The blanket
disable broke usage/cost tracking for all non-OpenAI providers.

supportsDeveloperRole is still forced off for non-native endpoints since
the developer message role is genuinely OpenAI-specific.

Users on backends that reject stream_options can opt out with
compat.supportsUsageInStreaming: false in their model config.

Fixes openclaw#46142
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

13 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

8 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd34387874

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1393 to 1397
broadcastChatFinal({
context,
runId: clientRunId,
sessionKey: rawSessionKey,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve /btw reply fallback for chat-only clients

This branch emits a chat.side_result and then sends chat final without a message, which means clients that only consume chat events will lose the /btw answer entirely; I verified ui/src/ui/app-gateway.ts only handles agent and chat events and has no chat.side_result handler. Because BTW responses are not persisted to history, those clients cannot recover the answer after the final event, so /btw appears to do nothing outside updated consumers (currently TUI).

Useful? React with 👍 / 👎.

frankekn pushed a commit that referenced this pull request Mar 15, 2026
The telegramPlugin.outbound.sendPayload in channel.ts is the actual
production path used by the plugin registry. It was not forwarding
forceDocument, making the fix in outbound-adapter.ts ineffective.

- Add forceDocument to buildTelegramSendOptions params and output
- Destructure and pass forceDocument in sendPayload

Addresses Codex review on #46119.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: android App: android app: macos App: macos app: web-ui App: web-ui channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser cli CLI command changes commands Command implementations docker Docker and sandbox tooling docs Improvements or additions to documentation gateway Gateway runtime scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.