From 6f36fbfc76df79410531cc74eee07dc408da40a2 Mon Sep 17 00:00:00 2001 From: Ross Shannon <6339-rossshannon@users.noreply.gitlab.com> Date: Thu, 29 Jan 2026 23:59:17 +0000 Subject: [PATCH 1/3] fix(slack): convert Unicode emojis to Slack shortcodes for reactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add node-emoji dependency for Unicode → shortcode conversion - Update normalizeEmoji() to handle both Unicode (👀) and shortcode (:eyes:) formats - Fixes issue where Unicode emojis in config (messages.ackReaction) would fail with 'invalid_name' error - Backwards compatible: existing shortcode configs continue to work Resolves the root cause by making Slack emoji format transparent to users. --- package.json | 1 + pnpm-lock.yaml | 135 ++++++++++++++++--------------------------- src/slack/actions.ts | 14 +++++ 3 files changed, 64 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index 4d38edf18..7d6053e8f 100644 --- a/package.json +++ b/package.json @@ -194,6 +194,7 @@ "long": "5.3.2", "markdown-it": "^14.1.0", "node-edge-tts": "^1.2.9", + "node-emoji": "^2.2.0", "osc-progress": "^0.3.0", "pdfjs-dist": "^5.4.530", "playwright-core": "1.58.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c0f99928..5a419beb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,6 +133,9 @@ importers: node-edge-tts: specifier: ^1.2.9 version: 1.2.9 + node-emoji: + specifier: ^2.2.0 + version: 2.2.0 osc-progress: specifier: ^0.3.0 version: 0.3.0 @@ -383,12 +386,12 @@ importers: '@microsoft/agents-hosting-extensions-teams': specifier: ^1.2.2 version: 1.2.2 - moltbot: - specifier: workspace:* - version: link:../.. express: specifier: ^5.2.1 version: 5.2.1 + moltbot: + specifier: workspace:* + version: link:../.. proper-lockfile: specifier: ^4.1.2 version: 4.1.2 @@ -2376,6 +2379,10 @@ packages: '@sinclair/typebox@0.34.47': resolution: {integrity: sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==} + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + '@slack/bolt@4.6.0': resolution: {integrity: sha512-xPgfUs2+OXSugz54Ky07pA890+Qydk22SYToi8uGpXeHSt1JWwFJkRyd/9Vlg5I1AdfdpGXExDpwnbuN9Q/2dQ==} engines: {node: '>=18', npm: '>=8.6.0'} @@ -3184,6 +3191,10 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + chmodrp@1.0.2: resolution: {integrity: sha512-TdngOlFV1FLTzU0o1w8MB6/BFywhtLC0SzRTGJU7T9lmdjlCWeMRt1iVo0Ki+ldwNk0BqNiKoc8xpLZEQ8mY1w==} @@ -3214,11 +3225,6 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - clawdbot@2026.1.24-3: - resolution: {integrity: sha512-zt9BzhWXduq8ZZR4rfzQDurQWAgmijTTyPZCQGrn5ew6wCEwhxxEr2/NHG7IlCwcfRsKymsY4se9KMhoNz0JtQ==} - engines: {node: '>=22.12.0'} - hasBin: true - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -3458,6 +3464,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -4418,6 +4427,10 @@ packages: resolution: {integrity: sha512-fvfW1dUgJdZAdTniC6MzLTMwnNUFKGKaUdRJ1OsveOYlfnPUETBU973CG89565txvbBowCQ4Czdeu3qSX8bNOg==} hasBin: true + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -5042,6 +5055,10 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + sleep-promise@9.1.0: resolution: {integrity: sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==} @@ -5193,6 +5210,7 @@ packages: tar@7.5.4: resolution: {integrity: sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} @@ -5322,6 +5340,10 @@ packages: resolution: {integrity: sha512-Heho1hJD81YChi+uS2RkSjcVO+EQLmLSyUlHyp7Y/wFbxQaGb4WXVKD073JytrjXJVkSZVzoE2MCSOKugFGtOQ==} engines: {node: '>=20.18.1'} + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + unicode-properties@1.4.1: resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} @@ -7917,6 +7939,8 @@ snapshots: '@sinclair/typebox@0.34.47': {} + '@sindresorhus/is@4.6.0': {} + '@slack/bolt@4.6.0(@types/express@5.0.6)': dependencies: '@slack/logger': 4.0.0 @@ -9062,6 +9086,8 @@ snapshots: chalk@5.6.2: {} + char-regex@1.0.2: {} + chmodrp@1.0.2: optional: true @@ -9098,84 +9124,6 @@ snapshots: dependencies: clsx: 2.1.1 - clawdbot@2026.1.24-3(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3): - dependencies: - '@agentclientprotocol/sdk': 0.13.1(zod@4.3.6) - '@aws-sdk/client-bedrock': 3.975.0 - '@buape/carbon': 0.14.0(hono@4.11.4) - '@clack/prompts': 0.11.0 - '@grammyjs/runner': 2.0.3(grammy@1.39.3) - '@grammyjs/transformer-throttler': 1.2.1(grammy@1.39.3) - '@homebridge/ciao': 1.3.4 - '@line/bot-sdk': 10.6.0 - '@lydell/node-pty': 1.2.0-beta.3 - '@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-coding-agent': 0.49.3(ws@8.19.0)(zod@4.3.6) - '@mariozechner/pi-tui': 0.49.3 - '@mozilla/readability': 0.6.0 - '@sinclair/typebox': 0.34.47 - '@slack/bolt': 4.6.0(@types/express@5.0.6) - '@slack/web-api': 7.13.0 - '@whiskeysockets/baileys': 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5) - ajv: 8.17.1 - body-parser: 2.2.2 - chalk: 5.6.2 - chokidar: 5.0.0 - chromium-bidi: 13.0.1(devtools-protocol@0.0.1561482) - cli-highlight: 2.1.11 - commander: 14.0.2 - croner: 9.1.0 - detect-libc: 2.1.2 - discord-api-types: 0.38.37 - dotenv: 17.2.3 - express: 5.2.1 - file-type: 21.3.0 - grammy: 1.39.3 - hono: 4.11.4 - jiti: 2.6.1 - json5: 2.2.3 - jszip: 3.10.1 - linkedom: 0.18.12 - long: 5.3.2 - markdown-it: 14.1.0 - node-edge-tts: 1.2.9 - osc-progress: 0.3.0 - pdfjs-dist: 5.4.530 - playwright-core: 1.58.0 - proper-lockfile: 4.1.2 - qrcode-terminal: 0.12.0 - sharp: 0.34.5 - sqlite-vec: 0.1.7-alpha.2 - tar: 7.5.4 - tslog: 4.10.2 - undici: 7.19.0 - ws: 8.19.0 - yaml: 2.8.2 - zod: 4.3.6 - optionalDependencies: - '@napi-rs/canvas': 0.1.88 - node-llama-cpp: 3.15.0(typescript@5.9.3) - transitivePeerDependencies: - - '@discordjs/opus' - - '@modelcontextprotocol/sdk' - - '@types/express' - - audio-decode - - aws-crt - - bufferutil - - canvas - - debug - - devtools-protocol - - encoding - - ffmpeg-static - - jimp - - link-preview-js - - node-opus - - opusscript - - supports-color - - typescript - - utf-8-validate - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -9405,6 +9353,8 @@ snapshots: emoji-regex@9.2.2: {} + emojilib@2.4.0: {} + encodeurl@2.0.0: {} entities@4.5.0: {} @@ -10481,6 +10431,13 @@ snapshots: - supports-color - utf-8-validate + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -11322,6 +11279,10 @@ snapshots: sisteransi@1.0.5: {} + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + sleep-promise@9.1.0: optional: true @@ -11583,6 +11544,8 @@ snapshots: undici@7.19.0: {} + unicode-emoji-modifier-base@1.0.0: {} + unicode-properties@1.4.1: dependencies: base64-js: 1.5.1 diff --git a/src/slack/actions.ts b/src/slack/actions.ts index 4ce6b37ca..32e30e4c3 100644 --- a/src/slack/actions.ts +++ b/src/slack/actions.ts @@ -1,4 +1,5 @@ import type { WebClient } from "@slack/web-api"; +import emoji from "node-emoji"; import { loadConfig } from "../config/config.js"; import { logVerbose } from "../globals.js"; @@ -47,11 +48,24 @@ function resolveToken(explicit?: string, accountId?: string) { return token; } +/** + * Normalize emoji for Slack reactions. + * Converts Unicode emojis to Slack shortcodes (👀 → eyes). + * Strips colons from already-formatted shortcodes (:eyes: → eyes). + */ function normalizeEmoji(raw: string) { const trimmed = raw.trim(); if (!trimmed) { throw new Error("Emoji is required for Slack reactions"); } + + // Try Unicode → shortcode conversion first + const shortcode = emoji.which(trimmed); + if (shortcode) { + return shortcode; + } + + // Fall back to stripping colons (for already-formatted shortcodes) return trimmed.replace(/^:+|:+$/g, ""); } From 99e35bdf49e4d6801c1bd26c6a7d8504858d8775 Mon Sep 17 00:00:00 2001 From: Ross Shannon <6339-rossshannon@users.noreply.gitlab.com> Date: Fri, 30 Jan 2026 00:10:25 +0000 Subject: [PATCH 2/3] fix: use named import for node-emoji --- src/slack/actions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slack/actions.ts b/src/slack/actions.ts index 32e30e4c3..c4525674c 100644 --- a/src/slack/actions.ts +++ b/src/slack/actions.ts @@ -1,5 +1,5 @@ import type { WebClient } from "@slack/web-api"; -import emoji from "node-emoji"; +import { which as emojiWhich } from "node-emoji"; import { loadConfig } from "../config/config.js"; import { logVerbose } from "../globals.js"; @@ -58,13 +58,13 @@ function normalizeEmoji(raw: string) { if (!trimmed) { throw new Error("Emoji is required for Slack reactions"); } - + // Try Unicode → shortcode conversion first - const shortcode = emoji.which(trimmed); + const shortcode = emojiWhich(trimmed); if (shortcode) { return shortcode; } - + // Fall back to stripping colons (for already-formatted shortcodes) return trimmed.replace(/^:+|:+$/g, ""); } From 5b13910276a5c60b9d9225718642ad5aaf37df30 Mon Sep 17 00:00:00 2001 From: Ross Shannon <6339-rossshannon@users.noreply.gitlab.com> Date: Fri, 30 Jan 2026 00:51:50 +0000 Subject: [PATCH 3/3] =?UTF-8?q?test:=20update=20test=20to=20expect=20norma?= =?UTF-8?q?lized=20emoji=20(eyes=20instead=20of=20=F0=9F=91=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...r.tool-result.forces-thread-replies-replytoid-is-set.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts b/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts index c4b4b96ed..b8e4bb168 100644 --- a/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts +++ b/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts @@ -107,7 +107,7 @@ describe("monitorSlackProvider tool results", () => { expect(reactMock).toHaveBeenCalledWith({ channel: "C1", timestamp: "456", - name: "👀", + name: "eyes", }); });