Skip to content

feat(imessage): add markdown.strip config option#7118

Open
PeterRosdahl wants to merge 4 commits intoopenclaw:mainfrom
PeterRosdahl:feat/strip-markdown-imessage-sms
Open

feat(imessage): add markdown.strip config option#7118
PeterRosdahl wants to merge 4 commits intoopenclaw:mainfrom
PeterRosdahl:feat/strip-markdown-imessage-sms

Conversation

@PeterRosdahl
Copy link

@PeterRosdahl PeterRosdahl commented Feb 2, 2026

Summary

Adds a new strip option to the markdown config that allows stripping markdown formatting from outbound messages.

This is useful for channels like iMessage and SMS that don't render markdown - raw asterisks and hashes look ugly to recipients.

Usage

channels:
  imessage:
    markdown:
      strip: true

The option can be set at channel level or per-account.

Changes

  • Added strip?: boolean to MarkdownConfig type
  • Added strip to MarkdownConfigSchema zod schema
  • Updated iMessage outbound to apply stripMarkdown() when enabled
  • Added tests for the new functionality

Since SMS goes through the same iMessage channel (Apple Messages handles both), this also covers SMS.

Closes #3846

Greptile Overview

Greptile Summary

Adds a markdown.strip option to the shared markdown config (types + Zod schema) and wires it into the iMessage outbound adapter so outbound text/captions can have markdown formatting removed when enabled. Includes a new Vitest suite covering default behavior, channel-level enablement, per-account overrides, header stripping, and media caption stripping.

Confidence Score: 3/5

  • Mostly safe to merge, with one likely runtime edge case in media sending when captions are omitted.
  • The change is small and covered by tests, but sendMedia applies markdown stripping unconditionally to text, which may be optional; if undefined, it can throw at runtime depending on adapter contract.
  • src/channels/plugins/outbound/imessage.ts

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

accountId,
});
const result = await send(to, text, {
const finalText = maybeStripMarkdown(text, cfg, accountId);
Copy link
Contributor

Choose a reason for hiding this comment

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

[P1] sendMedia currently always passes text through maybeStripMarkdown, but text is likely optional/undefined for media sends (many channels support media-without-caption). If text is undefined, stripMarkdown(text) will throw at runtime.

Consider guarding the caption:

Suggested change
const finalText = maybeStripMarkdown(text, cfg, accountId);
const finalText = text ? maybeStripMarkdown(text, cfg, accountId) : text;

(or adjust the types to guarantee text is always a string for media sends). Is text guaranteed to be a non-empty string for sendMedia, or is it optional/undefined when sending media without a caption?

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/channels/plugins/outbound/imessage.ts
Line: 31:31

Comment:
[P1] `sendMedia` currently always passes `text` through `maybeStripMarkdown`, but `text` is likely optional/undefined for media sends (many channels support media-without-caption). If `text` is `undefined`, `stripMarkdown(text)` will throw at runtime.

Consider guarding the caption:
```suggestion
    const finalText = text ? maybeStripMarkdown(text, cfg, accountId) : text;
```
(or adjust the types to guarantee `text` is always a string for media sends). Is `text` guaranteed to be a non-empty string for `sendMedia`, or is it optional/undefined when sending media without a caption?

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

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the review! This is already handled - maybeStripMarkdown returns early if text is falsy:

if (!text) return text;

And there's an explicit test case "handles undefined caption in media sends" that verifies this behavior works correctly with undefined captions.

@Jodameister
Copy link

Feedback: Additional Code Path Needed

Thanks for working on this! The markdown.strip option is definitely needed for iMessage/SMS.

Issue: Missing Monitor Path

The PR correctly patches the outbound adapter (channels/plugins/outbound/imessage.ts), which handles proactive sends via the message tool.

However, there's a second code path that also needs patching: imessage/monitor/deliver.ts

This file handles replies to incoming messages (the normal chat flow), which is the most common use case. Currently, the monitor path calls sendMessageIMessage() directly without going through the outbound adapter.

Without patching both paths, markdown.strip: true will only work for proactive sends, not for regular chat replies.

Suggestion: Link Handling

Additionally, consider how [link text](url) should be handled. The current stripMarkdown() converts it to just link text, but for iMessage it might be better to preserve the URL:

[Click here](https://example.com)  →  https://example.com

iMessage auto-linkifies URLs, so users still get a clickable link. Converting to just "Click here" loses the URL entirely.

Summary

  1. ✅ Outbound adapter patch is correct
  2. ❌ Monitor path (imessage/monitor/deliver.ts) needs the same treatment
  3. 💡 Consider [text](url)url instead of [text](url)text

Happy to help test if needed!

@openclaw-barnacle openclaw-barnacle bot added the channel: imessage Channel integration: imessage label Feb 3, 2026
@PeterRosdahl
Copy link
Author

Thanks for the thorough review @Jodameister! Both issues addressed:

1. Monitor path patched — Added stripMarkdown() to imessage/monitor/deliver.ts, applied after table conversion and before sending. Reads channel config (including per-account overrides) to check if markdown.strip is enabled.

2. Link handling improved[link text](url) now resolves to just the URL instead of losing it. iMessage auto-linkifies, so users get clickable links. Rule runs before other stripping to avoid conflicts with LINE's extractLinks flow.

Added test coverage for the new link behavior. Ready for another look!

@Jodameister
Copy link

Update: Code structure changed in v2026.2.x

Just updated to 2026.2.2-3 and noticed the source file structure has changed significantly.

Old structure (before bundling):

  • dist/imessage/monitor/deliver.js
  • dist/channels/plugins/outbound/imessage.js

New structure (bundled):

  • Everything is now in dist/loader-*.js (with hash suffix, e.g. loader-BrK9xPUo.js)
  • Both the monitor delivery and outbound adapter code are in this single bundled file

The patch locations are now:

  1. Around the sendMessageIMessage function (outbound)
  2. In the deliverReplies function for iMessage (monitor)

Both still call convertMarkdownTables() but need the additional stripMarkdown() call afterwards.

Note: The stripMarkdown() function already exists in the bundle (used for LINE messaging) and can be reused directly.

Just wanted to flag this in case the PR needs adjustment for the new bundled structure. 🙂

@Jodameister
Copy link

Small correction to my previous comment: After further testing, I found that the iMessage code is actually present in 3 separate bundles, not just one:

  1. extensionAPI.js
  2. loader-*.js (with hash suffix)
  3. reply-*.js (with hash suffix)

Each contains the sendMessageIMessage function and deliverReplies for iMessage, so all 3 need the patch applied.

The code structure is still the same (bundled), but it's split across multiple entry points rather than a single file.

@PeterRosdahl
Copy link
Author

Found the issue causing CI failures:

openclaw-2026-02-02.log was accidentally added to the PR (209 lines of repeated gateway startup errors). This is likely why build/lint checks are failing.

Quick fix:

git rm openclaw-2026-02-02.log
echo "*.log" >> .gitignore
git add .gitignore
git commit -m "chore: remove log file and ignore future logs"
git push

Re: Jodameister's bundling feedback:
Your source file changes are correct. The bundled structure in dist/ is generated during build, so your modifications to the source files should automatically be included when the bundles are rebuilt during CI.

The stripMarkdown() function already exists in the bundles (used for LINE), so your additions should work as-is once the log file is removed and CI can complete successfully.

@mudrii

This comment was marked as spam.

@PeterRosdahl PeterRosdahl force-pushed the feat/strip-markdown-imessage-sms branch from c065c3f to 0591553 Compare March 2, 2026 06:58
@PeterRosdahl
Copy link
Author

Re-triggered CI after flaky Bun download failure (502 from GitHub releases).

Peter R and others added 4 commits March 2, 2026 09:21
Adds a new 'strip' option to the markdown config that allows
stripping markdown formatting from outbound messages.

This is useful for channels like iMessage and SMS that don't
render markdown - raw asterisks and hashes look ugly to recipients.

Usage:
  channels:
    imessage:
      markdown:
        strip: true

The option can be set at channel level or per-account.

Closes openclaw#3846
…n links

- Apply stripMarkdown() in imessage/monitor/deliver.ts (the main chat
  reply path) when markdown.strip config is enabled. Previously only
  the outbound adapter path was patched.

- Change stripMarkdown() link handling: [text](url) now becomes just
  url instead of text. iMessage/SMS auto-linkify URLs, so this gives
  users clickable links instead of losing the URL entirely.
@PeterRosdahl PeterRosdahl force-pushed the feat/strip-markdown-imessage-sms branch from 9be3b7d to 5e07a29 Compare March 2, 2026 08:21
@PeterRosdahl
Copy link
Author

Rebased on latest main to pick up recent changes.

@PeterRosdahl
Copy link
Author

👋 Friendly ping! CI is green after rebase on main. This PR adds a markdown.strip config option for iMessage/SMS channels - would love a review when you have a moment. Thanks!

@openclaw-barnacle
Copy link

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: imessage Channel integration: imessage size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-strip markdown for channels that don't support it (iMessage, SMS)

3 participants