When skipMessagingToolDelivery is true (agent already sent via message
tool), the error guard early return was hardcoding delivered/
deliveryAttempted to false, overwriting the existing delivery state.
This caused cron metadata to report not-delivered even though the
message tool send already happened.
Now uses skipMessagingToolDelivery for the initial flag values in the
early return, matching the behavior of the rest of the function.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The delivery path prefers deliveryPayloads over synthesizedText when
payloads are non-empty. Error text arriving via deliveryPayloads
bypassed the synthesizedText-only guard. Now checks both paths —
payloads are only suppressed when every entry matches an error pattern.
Addresses review feedback from greptile-apps[bot].
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a cron job's model provider returns an error (e.g. HTTP 500), the
error JSON becomes the agent's synthesized text. With
`delivery.mode: "announce"`, this raw error dump was delivered to
user-facing channels (Discord, Telegram, etc.) as if it were normal
content.
Add an `isLikelyRawErrorOutput()` guard in `dispatchCronDelivery` that
detects common error output patterns (JSON error objects, JS exception
strings, HTTP status codes in error context) and skips delivery when
matched. The error is still logged internally and visible via
`cron runs`, but no longer posted to channels.
This makes the behavior consistent regardless of `bestEffort` setting —
error runs should never trigger announce delivery.
Closes#42243
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add --force-document to message.send for Telegram
Adds --force-document CLI flag to bypass sendPhoto and use sendDocument
instead, avoiding Telegram image compression for PNG/image files.
- TelegramSendOpts: add forceDocument field
- send.ts: skip sendPhoto when forceDocument=true (mediaSender pattern)
- ChannelOutboundContext: add forceDocument field
- telegramOutbound.sendMedia: pass forceDocument to sendMessageTelegram
- ChannelHandlerParams / DeliverOutboundPayloadsCoreParams: add forceDocument
- createChannelOutboundContextBase: propagate forceDocument
- outbound-send-service.ts: add forceDocument to executeSendAction params
- message-action-runner.ts: read forceDocument from params
- message.ts: add forceDocument to MessageSendParams
- register.send.ts: add --force-document CLI option
* fix: pass forceDocument through telegram action dispatch path
The actual send path goes through dispatchChannelMessageAction ->
telegramMessageActions.handleAction -> handleTelegramAction, not
deliverOutboundPayloads. forceDocument was not being read in
readTelegramSendParams or passed to sendMessageTelegram.
* fix: apply forceDocument to GIF branch to avoid sendAnimation
* fix: add disable_content_type_detection=true to sendDocument for --force-document
* fix: add forceDocument to buildSendSchema for agent discoverability
* fix: scope telegram force-document detection
* test: fix heartbeat target helper typing
* fix: skip image optimization when forceDocument is set
* fix: persist forceDocument in WAL queue for crash-recovery replay
* test: tighten heartbeat target test entry typing
---------
Co-authored-by: thepagent <thepagent@users.noreply.github.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
* refactor: remove channel shim directories, point all imports to extensions
Delete the 6 backward-compat shim directories (src/telegram, src/discord,
src/slack, src/signal, src/imessage, src/web) that were re-exporting from
extensions. Update all 112+ source files to import directly from
extensions/{channel}/src/ instead of through the shims.
Also:
- Move src/channels/telegram/ (allow-from, api) to extensions/telegram/src/
- Fix outbound adapters to use resolveOutboundSendDep (fixes 5 pre-existing TS errors)
- Update cross-extension imports (src/web/media.js → extensions/whatsapp/src/media.js)
- Update vitest, tsdown, knip, labeler, and script configs for new paths
- Update guard test allowlists for extension paths
After this, src/ has zero channel-specific implementation code — only the
generic plugin framework remains.
* fix: update raw-fetch guard allowlist line numbers after shim removal
* refactor: document direct extension channel imports
* test: mock transcript module in delivery helpers
* refactor: move Discord channel implementation to extensions/discord/src/
Move all Discord source files from src/discord/ to extensions/discord/src/,
following the extension migration pattern. Source files in src/discord/ are
replaced with re-export shims. Channel-plugin files from
src/channels/plugins/*/discord* are similarly moved and shimmed.
- Copy all .ts source files preserving subdirectory structure (monitor/, voice/)
- Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues)
- Fix all relative imports to use correct paths from new location
- Create re-export shims at original locations for backward compatibility
- Delete test files from shim locations (tests live in extension now)
- Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate
extension files outside src/
- Update write-plugin-sdk-entry-dts.ts to match new declaration output paths
* fix: add importOriginal to thread-bindings session-meta mock for extensions test
* style: fix formatting in thread-bindings lifecycle test
Move all Slack channel implementation files from src/slack/ to
extensions/slack/src/ and replace originals with shim re-exports.
This follows the extension migration pattern for channel plugins.
- Copy all .ts files to extensions/slack/src/ (preserving directory
structure: monitor/, http/, monitor/events/, monitor/message-handler/)
- Transform import paths: external src/ imports use relative paths
back to src/, internal slack imports stay relative within extension
- Replace all src/slack/ files with shim re-exports pointing to
the extension copies
- Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." so
the DTS build can follow shim chains into extensions/
- Update write-plugin-sdk-entry-dts.ts re-export path accordingly
- Preserve extensions/slack/index.ts, package.json, openclaw.plugin.json,
src/channel.ts, src/runtime.ts, src/channel.test.ts (untouched)
* refactor: move WhatsApp channel from src/web/ to extensions/whatsapp/
Move all WhatsApp implementation code (77 source/test files + 9 channel
plugin files) from src/web/ and src/channels/plugins/*/whatsapp* to
extensions/whatsapp/src/.
- Leave thin re-export shims at all original locations so cross-cutting
imports continue to resolve
- Update plugin-sdk/whatsapp.ts to only re-export generic framework
utilities; channel-specific functions imported locally by the extension
- Update vi.mock paths in 15 cross-cutting test files
- Rename outbound.ts -> send.ts to match extension naming conventions
and avoid false positive in cfg-threading guard test
- Widen tsconfig.plugin-sdk.dts.json rootDir to support shim->extension
cross-directory references
Part of the core-channels-to-extensions migration (PR 6/10).
* style: format WhatsApp extension files
* fix: correct stale import paths in WhatsApp extension tests
Fix vi.importActual, test mock, and hardcoded source paths that weren't
updated during the file move:
- media.test.ts: vi.importActual path
- onboarding.test.ts: vi.importActual path
- test-helpers.ts: test/mocks/baileys.js path
- monitor-inbox.test-harness.ts: incomplete media/store mock
- login.test.ts: hardcoded source file path
- message-action-runner.media.test.ts: vi.mock/importActual path
Move all Signal channel implementation files from src/signal/ to
extensions/signal/src/ and replace originals with re-export shims.
This continues the channel plugin migration pattern used by other
extensions, keeping backward compatibility via shims while the real
code lives in the extension.
- Copy 32 .ts files (source + tests) to extensions/signal/src/
- Transform all relative import paths for the new location
- Create 2-line re-export shims in src/signal/ for each moved file
- Preserve existing extension files (channel.ts, runtime.ts, etc.)
- Change tsconfig.plugin-sdk.dts.json rootDir from "src" to "."
to support cross-boundary re-exports from extensions/
* refactor: make OutboundSendDeps dynamic with channel-ID keys
Replace hardcoded per-channel send fields (sendTelegram, sendDiscord,
etc.) with a dynamic index-signature type keyed by channel ID. This
unblocks moving channel implementations to extensions without breaking
the outbound dispatch contract.
- OutboundSendDeps and CliDeps are now { [channelId: string]: unknown }
- Each outbound adapter resolves its send fn via bracket access with cast
- Lazy-loading preserved via createLazySender with module cache
- Delete 6 deps-send-*.runtime.ts one-liner re-export files
- Harden guardrail scan against deleted-but-tracked files
* fix: preserve outbound send-deps compatibility
* style: fix formatting issues (import order, extra bracket, trailing whitespace)
* fix: resolve type errors from dynamic OutboundSendDeps in tests and extension
* fix: remove unused OutboundSendDeps import from deliver.test-helpers
feat(cron): support persistent session targets for cron jobs (#9765)
Add support for `sessionTarget: "current"` and `session:<id>` so cron jobs can
bind to the creating session or a persistent named session instead of only
`main` or ephemeral `isolated` sessions.
Also:
- preserve custom session targets across reloads and restarts
- update gateway validation and normalization for the new target forms
- add cron coverage for current/custom session targets and fallback behavior
- fix merged CI regressions in Discord and diffs tests
- add a changelog entry for the new cron session behavior
Co-authored-by: kkhomej33-netizen <kkhomej33-netizen@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>