mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 07:43:55 +00:00
fix(discord): keep command previews emoji-safe (#99539)
This commit is contained in:
@@ -2,7 +2,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { discordApprovalNativeRuntime } from "./approval-handler.runtime.js";
|
||||
|
||||
async function buildExecApprovalPayloadText(commandText: string): Promise<string> {
|
||||
const pending = await discordApprovalNativeRuntime.presentation.buildPendingPayload({
|
||||
cfg: {} as never,
|
||||
accountId: "main",
|
||||
context: {
|
||||
token: "discord-token",
|
||||
config: {} as never,
|
||||
},
|
||||
request: {
|
||||
id: "approval-1",
|
||||
request: {
|
||||
command: commandText,
|
||||
},
|
||||
createdAtMs: 0,
|
||||
expiresAtMs: 1_000,
|
||||
},
|
||||
approvalKind: "exec",
|
||||
nowMs: 0,
|
||||
view: {
|
||||
approvalKind: "exec",
|
||||
phase: "pending",
|
||||
approvalId: "approval-1",
|
||||
title: "Exec Approval Required",
|
||||
commandText,
|
||||
commandPreview: null,
|
||||
expiresAtMs: 1_000,
|
||||
metadata: [],
|
||||
actions: [
|
||||
{
|
||||
label: "Allow",
|
||||
decision: "allow-once",
|
||||
style: "success",
|
||||
command: "/approve approval-1 allow-once",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return JSON.stringify(pending);
|
||||
}
|
||||
|
||||
describe("discordApprovalNativeRuntime", () => {
|
||||
it("does not split emoji graphemes when truncating exec command previews", async () => {
|
||||
const prefix = "a".repeat(999);
|
||||
|
||||
await expect(buildExecApprovalPayloadText(`${prefix}😀x`)).resolves.toContain(`${prefix}...`);
|
||||
await expect(buildExecApprovalPayloadText(`${prefix}🇺🇸x`)).resolves.toContain(`${prefix}...`);
|
||||
});
|
||||
|
||||
it("routes origin approval updates to the Discord thread channel when threadId is present", async () => {
|
||||
const prepared = await discordApprovalNativeRuntime.transport.prepareTarget({
|
||||
cfg: {} as never,
|
||||
|
||||
@@ -159,10 +159,38 @@ function buildExecApprovalPayload(container: DiscordUiContainer): MessagePayload
|
||||
return { components };
|
||||
}
|
||||
|
||||
const commandPreviewSegmenter =
|
||||
typeof Intl !== "undefined" && "Segmenter" in Intl
|
||||
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
||||
: null;
|
||||
|
||||
function* iterateCommandPreviewSegments(commandText: string): Iterable<string> {
|
||||
if (!commandPreviewSegmenter) {
|
||||
yield* Array.from(commandText);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (const segment of commandPreviewSegmenter.segment(commandText)) {
|
||||
yield segment.segment;
|
||||
}
|
||||
} catch {
|
||||
yield* Array.from(commandText);
|
||||
}
|
||||
}
|
||||
|
||||
function truncateCommandPreview(commandText: string, maxChars: number): string {
|
||||
let commandRaw = "";
|
||||
for (const segment of iterateCommandPreviewSegments(commandText)) {
|
||||
if (commandRaw.length + segment.length > maxChars) {
|
||||
return `${commandRaw}...`;
|
||||
}
|
||||
commandRaw += segment;
|
||||
}
|
||||
return commandText;
|
||||
}
|
||||
|
||||
function formatCommandPreview(commandText: string, maxChars: number): string {
|
||||
const commandRaw =
|
||||
commandText.length > maxChars ? `${commandText.slice(0, maxChars)}...` : commandText;
|
||||
return commandRaw.replace(/`/g, "\u200b`");
|
||||
return truncateCommandPreview(commandText, maxChars).replace(/`/g, "\u200b`");
|
||||
}
|
||||
|
||||
function formatOptionalCommandPreview(
|
||||
|
||||
Reference in New Issue
Block a user