




















@@ -33,11 +33,190 @@ function readChatUpdatePayload(
3333return payload as ChatUpdatePayload;
3434}
353536+const UNPAIRED_SURROGATE_RE =
37+/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
38+39+function readMrkdwnTexts(blocks: unknown): string[] {
40+if (!Array.isArray(blocks)) {
41+return [];
42+}
43+44+const texts: string[] = [];
45+for (const block of blocks) {
46+if (!block || typeof block !== "object") {
47+continue;
48+}
49+50+const text = (block as { text?: unknown }).text;
51+if (
52+text &&
53+typeof text === "object" &&
54+(text as { type?: unknown }).type === "mrkdwn" &&
55+typeof (text as { text?: unknown }).text === "string"
56+) {
57+texts.push((text as { text: string }).text);
58+}
59+60+const elements = (block as { elements?: unknown }).elements;
61+if (!Array.isArray(elements)) {
62+continue;
63+}
64+for (const element of elements) {
65+if (
66+element &&
67+typeof element === "object" &&
68+(element as { type?: unknown }).type === "mrkdwn" &&
69+typeof (element as { text?: unknown }).text === "string"
70+) {
71+texts.push((element as { text: string }).text);
72+}
73+}
74+}
75+76+return texts;
77+}
78+79+function findApprovalMrkdwn(payload: SlackPayload, prefix: string): string {
80+const text = readMrkdwnTexts(payload.blocks).find((entry) => entry.startsWith(prefix));
81+if (!text) {
82+throw new Error(`Expected Slack mrkdwn block starting with ${prefix}`);
83+}
84+return text;
85+}
86+3687describe("slackApprovalNativeRuntime", () => {
3788it("subscribes to plugin approval events", () => {
3889expect(slackApprovalNativeRuntime.eventKinds).toEqual(["exec", "plugin"]);
3990});
409192+it("does not leave dangling surrogates when truncating exec approval command mrkdwn", async () => {
93+const commandText = `${"a".repeat(2598)}😀tail`;
94+const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
95+cfg: {} as never,
96+accountId: "default",
97+context: {
98+app: {} as never,
99+config: {} as never,
100+},
101+request: {
102+id: "req-surrogate",
103+request: {
104+command: commandText,
105+},
106+createdAtMs: 0,
107+expiresAtMs: 60_000,
108+},
109+approvalKind: "exec",
110+nowMs: 0,
111+view: {
112+approvalKind: "exec",
113+approvalId: "req-surrogate",
114+ commandText,
115+metadata: [],
116+actions: [
117+{
118+decision: "allow-once",
119+label: "Allow Once",
120+command: "/approve req-surrogate allow-once",
121+style: "success",
122+},
123+],
124+} as never,
125+})) as SlackPayload;
126+127+const commandMrkdwn = findApprovalMrkdwn(payload, "*Command*");
128+expect(commandMrkdwn).toMatch(/…\n```$/);
129+expect(UNPAIRED_SURROGATE_RE.test(commandMrkdwn)).toBe(false);
130+});
131+132+it("does not leave dangling surrogates when truncating plugin approval request mrkdwn", async () => {
133+const title = `${"a".repeat(2598)}😀tail`;
134+const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
135+cfg: {} as never,
136+accountId: "default",
137+context: {
138+app: {} as never,
139+config: {} as never,
140+},
141+request: {
142+id: "plugin:req-surrogate",
143+request: {
144+ title,
145+description: "Needs approval.",
146+},
147+createdAtMs: 0,
148+expiresAtMs: 60_000,
149+},
150+approvalKind: "plugin",
151+nowMs: 0,
152+view: {
153+approvalKind: "plugin",
154+phase: "pending",
155+approvalId: "plugin:req-surrogate",
156+ title,
157+description: "Needs approval.",
158+severity: "warning",
159+pluginId: "test-plugin",
160+toolName: "test-tool",
161+metadata: [],
162+actions: [
163+{
164+decision: "deny",
165+label: "Deny",
166+command: "/approve plugin:req-surrogate deny",
167+style: "danger",
168+},
169+],
170+expiresAtMs: 60_000,
171+} as never,
172+})) as SlackPayload;
173+174+const requestMrkdwn = findApprovalMrkdwn(payload, "*Request*");
175+expect(requestMrkdwn).toMatch(/…$/);
176+expect(UNPAIRED_SURROGATE_RE.test(requestMrkdwn)).toBe(false);
177+});
178+179+it("still truncates plain BMP approval mrkdwn at the Slack approval preview limit", async () => {
180+const commandText = "b".repeat(2700);
181+const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
182+cfg: {} as never,
183+accountId: "default",
184+context: {
185+app: {} as never,
186+config: {} as never,
187+},
188+request: {
189+id: "req-bmp",
190+request: {
191+command: commandText,
192+},
193+createdAtMs: 0,
194+expiresAtMs: 60_000,
195+},
196+approvalKind: "exec",
197+nowMs: 0,
198+view: {
199+approvalKind: "exec",
200+approvalId: "req-bmp",
201+ commandText,
202+metadata: [],
203+actions: [
204+{
205+decision: "allow-once",
206+label: "Allow Once",
207+command: "/approve req-bmp allow-once",
208+style: "success",
209+},
210+],
211+} as never,
212+})) as SlackPayload;
213+214+const commandMrkdwn = findApprovalMrkdwn(payload, "*Command*");
215+expect(commandMrkdwn).toMatch(/…\n```$/);
216+expect(commandMrkdwn).toContain(`${"b".repeat(2599)}…`);
217+expect(UNPAIRED_SURROGATE_RE.test(commandMrkdwn)).toBe(false);
218+});
219+41220it("renders only the allowed pending actions", async () => {
42221const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
43222cfg: {} as never,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。