Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
### Fixes

- **Android hardware keyboard chat:** send with unmodified Enter on physical keyboards while preserving Shift+Enter and other modified Enter combinations for multiline input. (#101239) Thanks @3ninyt3nin-creator.
- **CJK Markdown emphasis:** render adjacent Chinese, Japanese, and Korean emphasis punctuation through the shared Markdown pipeline instead of leaking literal markers across channels. (#101230, #101120) Thanks @nicknmorty.
- **Codex yielded native subagents:** keep the parent app-server subscription and shared client alive until yielded native subagent completion delivery settles, preventing lost wakeups and leaked one-shot cleanup.
- **Discord streamed finals:** send completion replies as fresh messages so inactive channels become unread, while preserving targeted mentions without escalating `@everyone` or `@here`. (#99711, #99662) Thanks @davelutztx.
- **OpenAI-compatible SSE parsing:** recognize event streams mislabeled as JSON without prepending a second `data:` prefix, preserving valid streamed responses from non-conforming providers. (#96503) Thanks @ZengWen-DT.
Expand Down
6 changes: 6 additions & 0 deletions extensions/telegram/src/format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ describe("markdownToTelegramHtml", () => {
expect(markdownToTelegramRichHtml("<sup>1</sup>")).toBe("<sup>1</sup>");
});

it("renders bold spans that close before CJK punctuation in rich markdown", () => {
expect(markdownToTelegramRichHtml("边界:**社区显示:**Fable 与 **有效**。")).toBe(
"边界:<b>社区显示:</b>Fable 与 <b>有效</b>。",
);
});

it("materializes inline and paragraph newlines as <br> for rich messages", () => {
// The exact reported symptom: literal "• " bullets (not Markdown list markers)
// joined by soft breaks, which Bot API 10.1 rich messages collapse without <br>.
Expand Down
1 change: 1 addition & 0 deletions packages/markdown-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
},
"dependencies": {
"markdown-it": "14.3.0",
"markdown-it-cjk-friendly": "2.0.2",
"yaml": "2.9.0"
}
}
68 changes: 68 additions & 0 deletions packages/markdown-core/src/ir.emphasis-cjk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Markdown Core tests cover CJK-friendly emphasis flanking.
import { describe, expect, it } from "vitest";
import { markdownToIR } from "./ir.js";

function styledText(markdown: string, style: "bold" | "italic" = "bold"): string[] {
const ir = markdownToIR(markdown);
return ir.styles
.filter((span) => span.style === style)
.map((span) => ir.text.slice(span.start, span.end));
}

describe("markdownToIR CJK emphasis flanking", () => {
it("closes strong emphasis before adjacent Chinese text", () => {
expect(styledText("前**加粗:**后")).toEqual(["加粗:"]);
});

it("closes strong emphasis before adjacent Japanese text", () => {
expect(styledText("これは**強調。**です")).toEqual(["強調。"]);
});

it("closes strong emphasis before adjacent Korean text", () => {
expect(styledText("이것은 **강조:**입니다")).toEqual(["강조:"]);
});

it("handles supplementary CJK and variation selectors at emphasis boundaries", () => {
expect(styledText("𰻞𰻞**(ビャンビャン)**麺")).toEqual(["(ビャンビャン)"]);
expect(styledText("葛󠄀**(こちらが正式表記)**城市")).toEqual(["(こちらが正式表記)"]);
});

it("supports CJK-friendly underscore flanking without enabling Latin intraword emphasis", () => {
expect(styledText("__注意__:注意事項")).toEqual(["注意"]);
expect(styledText("foo_bar_baz", "italic")).toEqual([]);
});

it("keeps ASCII CommonMark emphasis behavior", () => {
expect(styledText("**bold** text")).toEqual(["bold"]);
expect(styledText("foo**bar**baz")).toEqual(["bar"]);
});

it("treats ideographic space (U+3000) as whitespace, not a flanking CJK char", () => {
// Opening delimiter followed by U+3000 must not be forced open.
expect(styledText("前**\u3000加粗**后")).toEqual([]);
// U+3000-separated emphasis keeps normal CommonMark behavior.
expect(styledText("前\u3000**加粗**\u3000后")).toEqual(["加粗"]);
});

it("treats Unicode thin space (U+2009) as whitespace in delimiter scanning", () => {
// Opening delimiter followed by U+2009 must not be forced open.
expect(styledText("前**\u2009加粗**后")).toEqual([]);
// Closing delimiter after CJK punctuation still closes when followed by U+2009.
expect(styledText("前**加粗:**\u2009后")).toEqual(["加粗:"]);
});

it("leaves code spans and links on their existing paths", () => {
const code = markdownToIR("`前**加粗:**后`");
expect(code.text).toBe("前**加粗:**后");
expect(code.styles.map((span) => span.style)).toEqual(["code"]);

const linked = markdownToIR("[前**加粗:**后](https://example.com)");
expect(linked.text).toBe("前加粗:后");
expect(linked.links).toEqual([
{ start: 0, end: linked.text.length, href: "https://example.com" },
]);
expect(linked.styles.filter((span) => span.style === "bold")).toEqual([
{ start: 1, end: 4, style: "bold" },
]);
});
});
2 changes: 2 additions & 0 deletions packages/markdown-core/src/ir.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Markdown Core module implements ir behavior.
import MarkdownIt from "markdown-it";
import markdownItCjkFriendly from "markdown-it-cjk-friendly";
import { chunkText } from "./chunk-text.js";
import type { MarkdownTableMode } from "./types.js";

Expand Down Expand Up @@ -150,6 +151,7 @@ function createMarkdownIt(options: MarkdownParseOptions): MarkdownIt {
breaks: false,
typographer: false,
});
md.use(markdownItCjkFriendly);
md.enable("strikethrough");
if (options.tableMode && options.tableMode !== "off") {
md.enable("table");
Expand Down
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading