Skip to content

长文本回复未接入 OpenClaw chunking contract,缺少 Markdown-safe 分块 #165

@g199209

Description

@g199209

问题描述

@tencent-weixin/openclaw-weixin@2.4.3 在发送较长微信回复时,当前缺少可靠的 Markdown-safe 分块流程。

OpenClaw core 已经提供了 channel outbound chunking contract:channel adapter 可以通过 outbound.chunkeroutbound.chunkerModetextChunkLimit 等字段声明如何在发送前拆分文本。

openclaw-weixin 当前的 outbound adapter 只声明了 textChunkLimit,没有声明 chunker / chunkerMode。在 OpenClaw core 的通用 outbound delivery 路径中,实际分块依赖 adapter 提供 chunker;如果没有 chunker,文本会作为单条消息进入 sendText

因此,openclaw-weixin 没有实际接入 OpenClaw core 的 outbound chunking contract。

相关代码路径

当前 openclaw-weixin 的 outbound adapter 大致是:

outbound: {
  deliveryMode: "direct",
  textChunkLimit: 4000,
  sendText: async (ctx) => {
    // ...
  },
}

这里缺少:

chunker: ...,
chunkerMode: "markdown",

此外,普通入站回复路径使用了插件自定义的 reply dispatcher delivery:

createReplyDispatcherWithTyping({
  deliver: async (payload) => {
    const rawText = payload.text ?? "";
    const f = new StreamingMarkdownFilter();
    const text = f.feed(rawText) + f.flush();

    await sendMessageWeixin({
      to: ctx.To,
      text,
      opts: { ... },
    });
  },
});

这个路径中:

  1. deliver 内部没有执行文本分块。
  2. sendMessageWeixin 只负责发送单条文本消息,没有分块逻辑。
  3. StreamingMarkdownFilter 是 Markdown 过滤/转换器,不是 chunker。
  4. 每次 deliver 都会新建一个 StreamingMarkdownFilter,无法保留跨 chunk 的 Markdown 状态。
  5. dispatchReplyFromConfig 调用中设置了:
replyOptions: { ...replyOptions, disableBlockStreaming: true }

这会关闭 core 的 block streaming chunker。

实际影响

当 AI 回复较长,尤其包含 Markdown 内容时,可能出现:

  • 微信消息发送失败
  • 微信侧截断
  • 用户只收到部分回复
  • fenced code block 被切坏
  • inline code / link / bold / italic 标记被破坏
  • Markdown 表格、列表结构显示异常
  • 过滤器在分片边界处无法正确处理 Markdown 状态

容易受影响的内容包括:

  • fenced code block
  • inline code
  • Markdown link
  • bold / italic
  • table
  • list
  • 长中文段落
  • emoji 或其他多字节字符

期望行为

希望 openclaw-weixin 在发送微信文本消息前提供统一、可靠的分块能力:

  1. 长文本回复自动拆分为多条微信消息。
  2. 分块逻辑适配微信侧单条消息长度限制。
  3. 优先按自然边界拆分:
    • 段落边界
    • 换行
    • 空格
    • 最后才硬切
  4. 分块过程尽量保持 Markdown 结构完整。
  5. fenced code block 不应被直接截断;必要时应 close/reopen code fence。
  6. 尽量避免破坏 inline code、链接、粗体、斜体等 Markdown 结构。
  7. 不产生空白 chunk。
  8. 普通回复路径和 outbound sendText 路径使用一致的分块逻辑。
  9. 发送层检查微信/iLink API 返回体中的业务错误码,而不只是 HTTP 状态。

可能的修复方向

方案 A:接入 OpenClaw outbound chunking contract

openclaw-weixinoutbound adapter 中声明 chunkerchunkerMode

outbound: {
  deliveryMode: "direct",
  textChunkLimit: ...,
  chunker: ...,
  chunkerMode: "markdown",
  sendText: async (ctx) => {
    // ...
  },
}

这样 OpenClaw core 的通用 outbound delivery 路径可以在调用 sendText 前完成分块。

方案 B:普通回复路径复用同一套 chunker

process-message.ts 的自定义 deliver 路径中,在调用 StreamingMarkdownFiltersendMessageWeixin 前显式执行 chunking。

推荐顺序是:

raw markdown
  -> Markdown-safe chunking
  -> per-chunk StreamingMarkdownFilter
  -> sendMessageWeixin per chunk

避免先简单截断文本,再对每段独立运行 filter。

方案 C:实现 WeChat 专用 chunker

如果 core 现有 chunker 无法完全覆盖微信需求,可以在插件侧实现专用 chunker:

  • 支持微信安全长度阈值
  • 支持 UTF-8 byte-aware 计算,或至少允许配置安全阈值
  • 优先按段落、换行、空格拆分
  • 保护 fenced code block
  • 尽量保护 inline Markdown
  • 过滤空 chunk
  • 每个 chunk 单独发送
  • 必要时加入发送间隔,避免微信侧限流或丢消息

总结

openclaw-weixin 当前缺少可靠的长文本分块发送流程:

  1. outbound adapter 没有声明 chunker / chunkerMode,未实际接入 OpenClaw outbound chunking contract。
  2. 普通回复路径使用自定义 deliver,内部没有分块。
  3. StreamingMarkdownFilter 只负责 Markdown 过滤/转换,不负责分块。
  4. block streaming chunker 在普通回复路径中被禁用。
  5. 插件侧没有实现微信专用 Markdown-safe chunker。

希望插件能在发送层统一处理微信长文本分块,避免长回复、Markdown 回复和代码块回复在微信侧失败或显示异常。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions