claude-threads 0.48.3 → 0.48.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.48.5] - 2026-01-08
11
+
12
+ ### Fixed
13
+ - **Slack msg_too_long errors fixed** - Messages are now safely truncated before sending to Slack API, preventing 4000+ character errors (#136)
14
+ - **Emoji conversion for Slack reactions** - Emoji names like `thumbsup` are now correctly converted to unicode for Slack reactions and Mattermost messages (#135)
15
+
16
+ ## [0.48.4] - 2026-01-08
17
+
18
+ ### Fixed
19
+ - **Code blocks no longer split incorrectly** - Messages are now split at line boundaries and never inside code blocks, removing ugly continuation markers (#134)
20
+ - **Disabled platforms show dim indicator** - Changed disabled platform status from red (error) to dim (inactive) for clearer visual feedback (#132)
21
+
22
+ ### Added
23
+ - **CI smoke test** - Added startup verification to CI pipeline to catch binary launch issues early (#133)
24
+
10
25
  ## [0.48.3] - 2026-01-08
11
26
 
12
27
  ### Changed
package/dist/index.js CHANGED
@@ -42542,6 +42542,155 @@ ${code}
42542
42542
  }
42543
42543
  }
42544
42544
 
42545
+ // src/platform/utils.ts
42546
+ function getPlatformIcon(platformType) {
42547
+ switch (platformType) {
42548
+ case "slack":
42549
+ return "\uD83D\uDCAC";
42550
+ case "mattermost":
42551
+ return "\uD83D\uDCE2";
42552
+ default:
42553
+ return "\uD83D\uDCAC";
42554
+ }
42555
+ }
42556
+ function truncateMessageSafely(message, maxLength, truncationIndicator = "... (truncated)") {
42557
+ if (message.length <= maxLength)
42558
+ return message;
42559
+ const reservedSpace = 4 + 2 + truncationIndicator.length;
42560
+ let truncated = message.substring(0, maxLength - reservedSpace);
42561
+ const codeBlockMarkers = (truncated.match(/```/g) || []).length;
42562
+ const isInsideCodeBlock = codeBlockMarkers % 2 === 1;
42563
+ if (isInsideCodeBlock) {
42564
+ truncated += "\n```";
42565
+ }
42566
+ return truncated + `
42567
+
42568
+ ` + truncationIndicator;
42569
+ }
42570
+ function normalizeEmojiName(emojiName) {
42571
+ const name = emojiName.replace(/^:|:$/g, "");
42572
+ const aliases = {
42573
+ thumbsup: "+1",
42574
+ thumbs_up: "+1",
42575
+ thumbsdown: "-1",
42576
+ thumbs_down: "-1",
42577
+ heavy_check_mark: "white_check_mark",
42578
+ x: "x",
42579
+ cross_mark: "x",
42580
+ heavy_multiplication_x: "x",
42581
+ pause_button: "pause",
42582
+ double_vertical_bar: "pause",
42583
+ play_button: "arrow_forward",
42584
+ stop_button: "stop",
42585
+ octagonal_sign: "stop",
42586
+ "1": "one",
42587
+ "2": "two",
42588
+ "3": "three",
42589
+ "4": "four",
42590
+ "5": "five"
42591
+ };
42592
+ return aliases[name.toLowerCase()] ?? name;
42593
+ }
42594
+ var EMOJI_NAME_TO_UNICODE = {
42595
+ "+1": "\uD83D\uDC4D",
42596
+ "-1": "\uD83D\uDC4E",
42597
+ white_check_mark: "\u2705",
42598
+ x: "\u274C",
42599
+ warning: "\u26A0\uFE0F",
42600
+ stop: "\uD83D\uDED1",
42601
+ pause: "\u23F8\uFE0F",
42602
+ arrow_forward: "\u25B6\uFE0F",
42603
+ one: "1\uFE0F\u20E3",
42604
+ two: "2\uFE0F\u20E3",
42605
+ three: "3\uFE0F\u20E3",
42606
+ four: "4\uFE0F\u20E3",
42607
+ five: "5\uFE0F\u20E3",
42608
+ six: "6\uFE0F\u20E3",
42609
+ seven: "7\uFE0F\u20E3",
42610
+ eight: "8\uFE0F\u20E3",
42611
+ nine: "9\uFE0F\u20E3",
42612
+ keycap_ten: "\uD83D\uDD1F",
42613
+ zero: "0\uFE0F\u20E3",
42614
+ robot: "\uD83E\uDD16",
42615
+ gear: "\u2699\uFE0F",
42616
+ lock: "\uD83D\uDD10",
42617
+ unlock: "\uD83D\uDD13",
42618
+ file_folder: "\uD83D\uDCC1",
42619
+ page_facing_up: "\uD83D\uDCC4",
42620
+ memo: "\uD83D\uDCDD",
42621
+ clock: "\u23F1\uFE0F",
42622
+ hourglass: "\u23F3",
42623
+ seedling: "\uD83C\uDF31",
42624
+ evergreen_tree: "\uD83C\uDF32",
42625
+ deciduous_tree: "\uD83C\uDF33",
42626
+ thread: "\uD83E\uDDF5",
42627
+ arrows_counterclockwise: "\uD83D\uDD04",
42628
+ package: "\uD83D\uDCE6",
42629
+ partying_face: "\uD83C\uDF89",
42630
+ hourglass_flowing_sand: "\u23F3",
42631
+ herb: "\uD83C\uDF3F",
42632
+ bust_in_silhouette: "\uD83D\uDC64",
42633
+ clipboard: "\uD83D\uDCCB",
42634
+ small_red_triangle_down: "\uD83D\uDD3D",
42635
+ arrow_down_small: "\uD83D\uDD3D",
42636
+ new: "\uD83C\uDD95"
42637
+ };
42638
+ var EMOJI_UNICODE_TO_NAME = Object.fromEntries(Object.entries(EMOJI_NAME_TO_UNICODE).map(([name, unicode]) => [unicode, name]));
42639
+ function convertUnicodeEmojiToShortcodes(text) {
42640
+ let result = text;
42641
+ for (const [unicode, name] of Object.entries(EMOJI_UNICODE_TO_NAME)) {
42642
+ result = result.split(unicode).join(`:${name}:`);
42643
+ }
42644
+ return result;
42645
+ }
42646
+ function getEmojiName(emoji) {
42647
+ const mapped = EMOJI_UNICODE_TO_NAME[emoji];
42648
+ if (mapped) {
42649
+ return mapped;
42650
+ }
42651
+ return emoji;
42652
+ }
42653
+ function convertMarkdownToSlack(content) {
42654
+ const codeBlocks = [];
42655
+ const CODE_BLOCK_PLACEHOLDER = "\x00CODE_BLOCK_";
42656
+ let preserved = content.replace(/```[\s\S]*?```/g, (match) => {
42657
+ const index = codeBlocks.length;
42658
+ codeBlocks.push(match);
42659
+ return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
42660
+ });
42661
+ preserved = preserved.replace(/`[^`\n]+`/g, (match) => {
42662
+ const index = codeBlocks.length;
42663
+ codeBlocks.push(match);
42664
+ return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
42665
+ });
42666
+ preserved = convertMarkdownTablesToSlack(preserved);
42667
+ preserved = preserved.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
42668
+ preserved = preserved.replace(/\*\*([^*]+)\*\*/g, "*$1*");
42669
+ preserved = preserved.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
42670
+ preserved = preserved.replace(/^[-*_]{3,}\s*$/gm, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
42671
+ for (let i = 0;i < codeBlocks.length; i++) {
42672
+ preserved = preserved.replace(`${CODE_BLOCK_PLACEHOLDER}${i}\x00`, codeBlocks[i]);
42673
+ }
42674
+ return preserved;
42675
+ }
42676
+ function convertMarkdownTablesToSlack(content) {
42677
+ const tableRegex = /^\|(.+)\|\s*\n\|[-:\s|]+\|\s*\n((?:\|.+\|\s*\n?)+)/gm;
42678
+ return content.replace(tableRegex, (_match, headerLine, bodyLines) => {
42679
+ const headers = headerLine.split("|").map((h) => h.trim()).filter((h) => h);
42680
+ const rows = bodyLines.trim().split(`
42681
+ `).map((row) => row.split("|").map((c) => c.trim()).filter((c) => c !== ""));
42682
+ const formattedRows = rows.map((row) => {
42683
+ const cells = row.map((cell, i) => {
42684
+ const header = headers[i];
42685
+ return header ? `*${header}:* ${cell}` : cell;
42686
+ });
42687
+ return cells.join(" \xB7 ");
42688
+ });
42689
+ return formattedRows.join(`
42690
+ `);
42691
+ });
42692
+ }
42693
+
42545
42694
  // src/platform/mattermost/client.ts
42546
42695
  var log = createLogger("mattermost");
42547
42696
  function escapeRegExp(string) {
@@ -42686,7 +42835,7 @@ class MattermostClient extends EventEmitter {
42686
42835
  async createPost(message, threadId) {
42687
42836
  const request = {
42688
42837
  channel_id: this.channelId,
42689
- message,
42838
+ message: convertUnicodeEmojiToShortcodes(message),
42690
42839
  root_id: threadId
42691
42840
  };
42692
42841
  const post = await this.api("POST", "/posts", request);
@@ -42695,7 +42844,7 @@ class MattermostClient extends EventEmitter {
42695
42844
  async updatePost(postId, message) {
42696
42845
  const request = {
42697
42846
  id: postId,
42698
- message
42847
+ message: convertUnicodeEmojiToShortcodes(message)
42699
42848
  };
42700
42849
  const post = await this.api("PUT", `/posts/${postId}`, request);
42701
42850
  return this.normalizePlatformPost(post);
@@ -43058,82 +43207,6 @@ class MattermostClient extends EventEmitter {
43058
43207
  // src/platform/slack/client.ts
43059
43208
  import { EventEmitter as EventEmitter2 } from "events";
43060
43209
 
43061
- // src/platform/utils.ts
43062
- function getPlatformIcon(platformType) {
43063
- switch (platformType) {
43064
- case "slack":
43065
- return "\uD83D\uDCAC";
43066
- case "mattermost":
43067
- return "\uD83D\uDCE2";
43068
- default:
43069
- return "\uD83D\uDCAC";
43070
- }
43071
- }
43072
- function normalizeEmojiName(emojiName) {
43073
- const name = emojiName.replace(/^:|:$/g, "");
43074
- const aliases = {
43075
- thumbsup: "+1",
43076
- thumbs_up: "+1",
43077
- thumbsdown: "-1",
43078
- thumbs_down: "-1",
43079
- heavy_check_mark: "white_check_mark",
43080
- x: "x",
43081
- cross_mark: "x",
43082
- heavy_multiplication_x: "x",
43083
- pause_button: "pause",
43084
- double_vertical_bar: "pause",
43085
- play_button: "arrow_forward",
43086
- stop_button: "stop",
43087
- octagonal_sign: "stop",
43088
- "1": "one",
43089
- "2": "two",
43090
- "3": "three",
43091
- "4": "four",
43092
- "5": "five"
43093
- };
43094
- return aliases[name.toLowerCase()] ?? name;
43095
- }
43096
- function convertMarkdownToSlack(content) {
43097
- const codeBlocks = [];
43098
- const CODE_BLOCK_PLACEHOLDER = "\x00CODE_BLOCK_";
43099
- let preserved = content.replace(/```[\s\S]*?```/g, (match) => {
43100
- const index = codeBlocks.length;
43101
- codeBlocks.push(match);
43102
- return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
43103
- });
43104
- preserved = preserved.replace(/`[^`\n]+`/g, (match) => {
43105
- const index = codeBlocks.length;
43106
- codeBlocks.push(match);
43107
- return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
43108
- });
43109
- preserved = convertMarkdownTablesToSlack(preserved);
43110
- preserved = preserved.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
43111
- preserved = preserved.replace(/\*\*([^*]+)\*\*/g, "*$1*");
43112
- preserved = preserved.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
43113
- preserved = preserved.replace(/^[-*_]{3,}\s*$/gm, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
43114
- for (let i = 0;i < codeBlocks.length; i++) {
43115
- preserved = preserved.replace(`${CODE_BLOCK_PLACEHOLDER}${i}\x00`, codeBlocks[i]);
43116
- }
43117
- return preserved;
43118
- }
43119
- function convertMarkdownTablesToSlack(content) {
43120
- const tableRegex = /^\|(.+)\|\s*\n\|[-:\s|]+\|\s*\n((?:\|.+\|\s*\n?)+)/gm;
43121
- return content.replace(tableRegex, (_match, headerLine, bodyLines) => {
43122
- const headers = headerLine.split("|").map((h) => h.trim()).filter((h) => h);
43123
- const rows = bodyLines.trim().split(`
43124
- `).map((row) => row.split("|").map((c) => c.trim()).filter((c) => c !== ""));
43125
- const formattedRows = rows.map((row) => {
43126
- const cells = row.map((cell, i) => {
43127
- const header = headers[i];
43128
- return header ? `*${header}:* ${cell}` : cell;
43129
- });
43130
- return cells.join(" \xB7 ");
43131
- });
43132
- return formattedRows.join(`
43133
- `);
43134
- });
43135
- }
43136
-
43137
43210
  // src/platform/slack/formatter.ts
43138
43211
  class SlackFormatter {
43139
43212
  formatBold(text) {
@@ -43738,9 +43811,10 @@ class SlackClient extends EventEmitter2 {
43738
43811
  }
43739
43812
  async createPost(message, threadId, options) {
43740
43813
  const shouldUnfurl = options?.unfurl ?? threadId !== undefined;
43814
+ const truncatedMessage = this.truncateMessageIfNeeded(message);
43741
43815
  const body = {
43742
43816
  channel: this.channelId,
43743
- text: message,
43817
+ text: truncatedMessage,
43744
43818
  unfurl_links: shouldUnfurl,
43745
43819
  unfurl_media: shouldUnfurl
43746
43820
  };
@@ -43759,10 +43833,11 @@ class SlackClient extends EventEmitter2 {
43759
43833
  };
43760
43834
  }
43761
43835
  async updatePost(postId, message) {
43836
+ const truncatedMessage = this.truncateMessageIfNeeded(message);
43762
43837
  const response = await this.api("POST", "chat.update", {
43763
43838
  channel: this.channelId,
43764
43839
  ts: postId,
43765
- text: message
43840
+ text: truncatedMessage
43766
43841
  });
43767
43842
  return {
43768
43843
  id: response.ts,
@@ -43841,6 +43916,14 @@ class SlackClient extends EventEmitter2 {
43841
43916
  getMessageLimits() {
43842
43917
  return { maxLength: 12000, hardThreshold: 1e4 };
43843
43918
  }
43919
+ truncateMessageIfNeeded(message) {
43920
+ const { maxLength } = this.getMessageLimits();
43921
+ if (message.length <= maxLength) {
43922
+ return message;
43923
+ }
43924
+ log2.warn(`Truncating message from ${message.length} to ~${maxLength} chars`);
43925
+ return truncateMessageSafely(message, maxLength, "_... (truncated)_");
43926
+ }
43844
43927
  async getThreadHistory(threadId, options) {
43845
43928
  try {
43846
43929
  const limit = options?.limit || 100;
@@ -43868,19 +43951,21 @@ class SlackClient extends EventEmitter2 {
43868
43951
  }
43869
43952
  }
43870
43953
  async addReaction(postId, emojiName) {
43871
- log2.debug(`Adding reaction :${emojiName}: to post ${postId.substring(0, 12)}`);
43954
+ const name = getEmojiName(emojiName);
43955
+ log2.debug(`Adding reaction :${name}: to post ${postId.substring(0, 12)}`);
43872
43956
  await this.api("POST", "reactions.add", {
43873
43957
  channel: this.channelId,
43874
43958
  timestamp: postId,
43875
- name: emojiName
43959
+ name
43876
43960
  });
43877
43961
  }
43878
43962
  async removeReaction(postId, emojiName) {
43879
- log2.debug(`Removing reaction :${emojiName}: from post ${postId.substring(0, 12)}`);
43963
+ const name = getEmojiName(emojiName);
43964
+ log2.debug(`Removing reaction :${name}: from post ${postId.substring(0, 12)}`);
43880
43965
  await this.api("POST", "reactions.remove", {
43881
43966
  channel: this.channelId,
43882
43967
  timestamp: postId,
43883
- name: emojiName
43968
+ name
43884
43969
  });
43885
43970
  }
43886
43971
  isBotMentioned(message) {
@@ -43961,14 +44046,14 @@ async function getUser(config, userId) {
43961
44046
  async function createPost(config, channelId, message, rootId) {
43962
44047
  return mattermostApi(config, "POST", "/posts", {
43963
44048
  channel_id: channelId,
43964
- message,
44049
+ message: convertUnicodeEmojiToShortcodes(message),
43965
44050
  root_id: rootId
43966
44051
  });
43967
44052
  }
43968
44053
  async function updatePost(config, postId, message) {
43969
44054
  return mattermostApi(config, "PUT", `/posts/${postId}`, {
43970
44055
  id: postId,
43971
- message
44056
+ message: convertUnicodeEmojiToShortcodes(message)
43972
44057
  });
43973
44058
  }
43974
44059
  async function addReaction(config, postId, userId, emojiName) {
@@ -45211,13 +45296,20 @@ async function bumpTasksToBottomWithContent(session, newContent, registerPost) {
45211
45296
  const oldTasksPostId = session.tasksPostId;
45212
45297
  const oldTasksContent = session.lastTasksContent;
45213
45298
  sessionLog(session).debug(`Bumping tasks to bottom, repurposing post ${oldTasksPostId.substring(0, 8)}`);
45299
+ const { maxLength: MAX_POST_LENGTH } = session.platform.getMessageLimits();
45300
+ let contentToPost = newContent;
45301
+ if (contentToPost.length > MAX_POST_LENGTH) {
45302
+ sessionLog(session).warn(`Content too long for repurposed post (${contentToPost.length}), truncating`);
45303
+ const formatter = session.platform.getFormatter();
45304
+ contentToPost = truncateMessageSafely(contentToPost, MAX_POST_LENGTH, formatter.formatItalic("... (truncated)"));
45305
+ }
45214
45306
  try {
45215
45307
  await session.platform.removeReaction(oldTasksPostId, TASK_TOGGLE_EMOJIS[0]);
45216
45308
  } catch (err) {
45217
45309
  sessionLog(session).debug(`Could not remove toggle emoji: ${err}`);
45218
45310
  }
45219
45311
  await session.platform.unpinPost(oldTasksPostId).catch(() => {});
45220
- await withErrorHandling(() => session.platform.updatePost(oldTasksPostId, newContent), { action: "Repurpose task post", session });
45312
+ await withErrorHandling(() => session.platform.updatePost(oldTasksPostId, contentToPost), { action: "Repurpose task post", session });
45221
45313
  registerPost(oldTasksPostId, session.threadId);
45222
45314
  if (oldTasksContent) {
45223
45315
  const displayContent = getTaskDisplayContent(session);
@@ -45281,6 +45373,7 @@ async function flush(session, registerPost) {
45281
45373
  const currentPostId = session.currentPostId;
45282
45374
  let breakPoint;
45283
45375
  let codeBlockLanguage;
45376
+ let codeBlockOpenPosition;
45284
45377
  if (content.length > HARD_CONTINUATION_THRESHOLD) {
45285
45378
  const startSearchPos = Math.floor(HARD_CONTINUATION_THRESHOLD * 0.7);
45286
45379
  const breakInfo = findLogicalBreakpoint(content, startSearchPos, Math.floor(HARD_CONTINUATION_THRESHOLD * 0.3));
@@ -45290,13 +45383,8 @@ async function flush(session, registerPost) {
45290
45383
  const codeBlockState = getCodeBlockState(content, startSearchPos);
45291
45384
  if (codeBlockState.isInside) {
45292
45385
  codeBlockLanguage = codeBlockState.language;
45293
- const searchWindow = content.substring(startSearchPos, HARD_CONTINUATION_THRESHOLD);
45294
- const lineBreakMatch = searchWindow.match(/\n/);
45295
- if (lineBreakMatch && lineBreakMatch.index !== undefined) {
45296
- breakPoint = startSearchPos + lineBreakMatch.index + 1;
45297
- } else {
45298
- breakPoint = HARD_CONTINUATION_THRESHOLD;
45299
- }
45386
+ codeBlockOpenPosition = codeBlockState.openPosition;
45387
+ breakPoint = HARD_CONTINUATION_THRESHOLD;
45300
45388
  } else {
45301
45389
  breakPoint = content.lastIndexOf(`
45302
45390
  `, HARD_CONTINUATION_THRESHOLD);
@@ -45319,35 +45407,46 @@ async function flush(session, registerPost) {
45319
45407
  return;
45320
45408
  }
45321
45409
  }
45322
- let firstPart = content.substring(0, breakPoint).trim();
45323
- let remainder = content.substring(breakPoint).trim();
45324
- if (codeBlockLanguage !== undefined) {
45325
- firstPart = firstPart + "\n```";
45326
- remainder = "```" + codeBlockLanguage + `
45327
- ` + remainder;
45410
+ if (codeBlockLanguage !== undefined && codeBlockOpenPosition !== undefined) {
45411
+ if (codeBlockOpenPosition === 0) {
45412
+ try {
45413
+ await session.platform.updatePost(currentPostId, content);
45414
+ } catch {
45415
+ sessionLog(session).debug("Update failed (code block at start), will create new post on next flush");
45416
+ session.currentPostId = null;
45417
+ }
45418
+ return;
45419
+ }
45420
+ const breakBeforeCodeBlock = content.lastIndexOf(`
45421
+ `, codeBlockOpenPosition);
45422
+ if (breakBeforeCodeBlock > 0) {
45423
+ breakPoint = breakBeforeCodeBlock;
45424
+ } else {
45425
+ try {
45426
+ await session.platform.updatePost(currentPostId, content);
45427
+ } catch {
45428
+ sessionLog(session).debug("Update failed (no break before code block), will create new post on next flush");
45429
+ session.currentPostId = null;
45430
+ }
45431
+ return;
45432
+ }
45328
45433
  }
45329
- const formatter2 = session.platform.getFormatter();
45330
- const firstPartWithMarker = remainder ? firstPart + `
45331
-
45332
- ` + formatter2.formatItalic("... (continued below)") : firstPart;
45434
+ const firstPart = content.substring(0, breakPoint).trim();
45435
+ const remainder = content.substring(breakPoint).trim();
45333
45436
  try {
45334
- await session.platform.updatePost(currentPostId, firstPartWithMarker);
45437
+ await session.platform.updatePost(currentPostId, firstPart);
45335
45438
  } catch {
45336
45439
  sessionLog(session).debug("Update failed during split, continuing with new post");
45337
45440
  }
45338
45441
  session.currentPostId = null;
45339
45442
  session.pendingContent = remainder;
45340
45443
  if (remainder) {
45341
- const continuationMarker = formatter2.formatItalic("(continued)");
45342
- const continuationContent = continuationMarker + `
45343
-
45344
- ` + remainder;
45345
45444
  const hasActiveTasks = session.tasksPostId && session.lastTasksContent && !session.tasksCompleted;
45346
45445
  if (hasActiveTasks) {
45347
- const postId = await bumpTasksToBottomWithContent(session, continuationContent, registerPost);
45446
+ const postId = await bumpTasksToBottomWithContent(session, remainder, registerPost);
45348
45447
  session.currentPostId = postId;
45349
45448
  } else {
45350
- const post = await withErrorHandling(() => session.platform.createPost(continuationContent, session.threadId), { action: "Create continuation post", session });
45449
+ const post = await withErrorHandling(() => session.platform.createPost(remainder, session.threadId), { action: "Create continuation post", session });
45351
45450
  if (post) {
45352
45451
  session.currentPostId = post.id;
45353
45452
  registerPost(post.id, session.threadId);
@@ -45358,11 +45457,9 @@ async function flush(session, registerPost) {
45358
45457
  return;
45359
45458
  }
45360
45459
  if (content.length > MAX_POST_LENGTH) {
45361
- sessionLog(session).warn(`Content too long (${content.length}), truncating to ${MAX_POST_LENGTH - 50}`);
45460
+ sessionLog(session).warn(`Content too long (${content.length}), truncating`);
45362
45461
  const formatter2 = session.platform.getFormatter();
45363
- content = content.substring(0, MAX_POST_LENGTH - 50) + `
45364
-
45365
- ` + formatter2.formatItalic("... (truncated)");
45462
+ content = truncateMessageSafely(content, MAX_POST_LENGTH, formatter2.formatItalic("... (truncated)"));
45366
45463
  }
45367
45464
  if (session.currentPostId) {
45368
45465
  const postId = session.currentPostId;
@@ -62854,7 +62951,10 @@ function Platforms({ platforms }) {
62854
62951
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
62855
62952
  children: getPlatformIcon(platform2.platformType || "mattermost")
62856
62953
  }, undefined, false, undefined, this),
62857
- platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
62954
+ !platform2.enabled ? /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
62955
+ dimColor: true,
62956
+ children: "\u25CB"
62957
+ }, undefined, false, undefined, this) : platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
62858
62958
  color: "yellow",
62859
62959
  children: "\u25CC"
62860
62960
  }, undefined, false, undefined, this) : platform2.connected ? /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
@@ -62865,7 +62965,8 @@ function Platforms({ platforms }) {
62865
62965
  children: "\u25CB"
62866
62966
  }, undefined, false, undefined, this),
62867
62967
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
62868
- color: "cyan",
62968
+ color: platform2.enabled ? "cyan" : undefined,
62969
+ dimColor: !platform2.enabled,
62869
62970
  children: [
62870
62971
  "@",
62871
62972
  platform2.botName
@@ -62876,6 +62977,7 @@ function Platforms({ platforms }) {
62876
62977
  children: "on"
62877
62978
  }, undefined, false, undefined, this),
62878
62979
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
62980
+ dimColor: !platform2.enabled,
62879
62981
  children: platform2.displayName
62880
62982
  }, undefined, false, undefined, this),
62881
62983
  platform2.reconnecting && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
@@ -65080,7 +65182,7 @@ async function main() {
65080
65182
  await sessionManager?.pauseSessionsForPlatform(platformId);
65081
65183
  client.disconnect();
65082
65184
  sessionStore2.setPlatformEnabled(platformId, false);
65083
- ui.setPlatformStatus(platformId, { connected: false });
65185
+ ui.setPlatformStatus(platformId, { connected: false, reconnecting: false });
65084
65186
  ui.addLog({ level: "info", component: "toggle", message: `\u2713 Platform ${platformId} disabled` });
65085
65187
  }
65086
65188
  },
@@ -33837,6 +33837,100 @@ ${code}
33837
33837
  }
33838
33838
  }
33839
33839
 
33840
+ // src/platform/utils.ts
33841
+ var EMOJI_NAME_TO_UNICODE = {
33842
+ "+1": "\uD83D\uDC4D",
33843
+ "-1": "\uD83D\uDC4E",
33844
+ white_check_mark: "\u2705",
33845
+ x: "\u274C",
33846
+ warning: "\u26A0\uFE0F",
33847
+ stop: "\uD83D\uDED1",
33848
+ pause: "\u23F8\uFE0F",
33849
+ arrow_forward: "\u25B6\uFE0F",
33850
+ one: "1\uFE0F\u20E3",
33851
+ two: "2\uFE0F\u20E3",
33852
+ three: "3\uFE0F\u20E3",
33853
+ four: "4\uFE0F\u20E3",
33854
+ five: "5\uFE0F\u20E3",
33855
+ six: "6\uFE0F\u20E3",
33856
+ seven: "7\uFE0F\u20E3",
33857
+ eight: "8\uFE0F\u20E3",
33858
+ nine: "9\uFE0F\u20E3",
33859
+ keycap_ten: "\uD83D\uDD1F",
33860
+ zero: "0\uFE0F\u20E3",
33861
+ robot: "\uD83E\uDD16",
33862
+ gear: "\u2699\uFE0F",
33863
+ lock: "\uD83D\uDD10",
33864
+ unlock: "\uD83D\uDD13",
33865
+ file_folder: "\uD83D\uDCC1",
33866
+ page_facing_up: "\uD83D\uDCC4",
33867
+ memo: "\uD83D\uDCDD",
33868
+ clock: "\u23F1\uFE0F",
33869
+ hourglass: "\u23F3",
33870
+ seedling: "\uD83C\uDF31",
33871
+ evergreen_tree: "\uD83C\uDF32",
33872
+ deciduous_tree: "\uD83C\uDF33",
33873
+ thread: "\uD83E\uDDF5",
33874
+ arrows_counterclockwise: "\uD83D\uDD04",
33875
+ package: "\uD83D\uDCE6",
33876
+ partying_face: "\uD83C\uDF89",
33877
+ hourglass_flowing_sand: "\u23F3",
33878
+ herb: "\uD83C\uDF3F",
33879
+ bust_in_silhouette: "\uD83D\uDC64",
33880
+ clipboard: "\uD83D\uDCCB",
33881
+ small_red_triangle_down: "\uD83D\uDD3D",
33882
+ arrow_down_small: "\uD83D\uDD3D",
33883
+ new: "\uD83C\uDD95"
33884
+ };
33885
+ var EMOJI_UNICODE_TO_NAME = Object.fromEntries(Object.entries(EMOJI_NAME_TO_UNICODE).map(([name, unicode]) => [unicode, name]));
33886
+ function convertUnicodeEmojiToShortcodes(text) {
33887
+ let result = text;
33888
+ for (const [unicode, name] of Object.entries(EMOJI_UNICODE_TO_NAME)) {
33889
+ result = result.split(unicode).join(`:${name}:`);
33890
+ }
33891
+ return result;
33892
+ }
33893
+ function convertMarkdownToSlack(content) {
33894
+ const codeBlocks = [];
33895
+ const CODE_BLOCK_PLACEHOLDER = "\x00CODE_BLOCK_";
33896
+ let preserved = content.replace(/```[\s\S]*?```/g, (match) => {
33897
+ const index = codeBlocks.length;
33898
+ codeBlocks.push(match);
33899
+ return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
33900
+ });
33901
+ preserved = preserved.replace(/`[^`\n]+`/g, (match) => {
33902
+ const index = codeBlocks.length;
33903
+ codeBlocks.push(match);
33904
+ return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
33905
+ });
33906
+ preserved = convertMarkdownTablesToSlack(preserved);
33907
+ preserved = preserved.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
33908
+ preserved = preserved.replace(/\*\*([^*]+)\*\*/g, "*$1*");
33909
+ preserved = preserved.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
33910
+ preserved = preserved.replace(/^[-*_]{3,}\s*$/gm, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
33911
+ for (let i = 0;i < codeBlocks.length; i++) {
33912
+ preserved = preserved.replace(`${CODE_BLOCK_PLACEHOLDER}${i}\x00`, codeBlocks[i]);
33913
+ }
33914
+ return preserved;
33915
+ }
33916
+ function convertMarkdownTablesToSlack(content) {
33917
+ const tableRegex = /^\|(.+)\|\s*\n\|[-:\s|]+\|\s*\n((?:\|.+\|\s*\n?)+)/gm;
33918
+ return content.replace(tableRegex, (_match, headerLine, bodyLines) => {
33919
+ const headers = headerLine.split("|").map((h) => h.trim()).filter((h) => h);
33920
+ const rows = bodyLines.trim().split(`
33921
+ `).map((row) => row.split("|").map((c) => c.trim()).filter((c) => c !== ""));
33922
+ const formattedRows = rows.map((row) => {
33923
+ const cells = row.map((cell, i) => {
33924
+ const header = headers[i];
33925
+ return header ? `*${header}:* ${cell}` : cell;
33926
+ });
33927
+ return cells.join(" \xB7 ");
33928
+ });
33929
+ return formattedRows.join(`
33930
+ `);
33931
+ });
33932
+ }
33933
+
33840
33934
  // src/mattermost/api.ts
33841
33935
  var log = createLogger("mm-api");
33842
33936
  async function mattermostApi(config2, method, path, body) {
@@ -33872,14 +33966,14 @@ async function getUser(config2, userId) {
33872
33966
  async function createPost(config2, channelId, message, rootId) {
33873
33967
  return mattermostApi(config2, "POST", "/posts", {
33874
33968
  channel_id: channelId,
33875
- message,
33969
+ message: convertUnicodeEmojiToShortcodes(message),
33876
33970
  root_id: rootId
33877
33971
  });
33878
33972
  }
33879
33973
  async function updatePost(config2, postId, message) {
33880
33974
  return mattermostApi(config2, "PUT", `/posts/${postId}`, {
33881
33975
  id: postId,
33882
- message
33976
+ message: convertUnicodeEmojiToShortcodes(message)
33883
33977
  });
33884
33978
  }
33885
33979
  async function addReaction(config2, postId, userId, emojiName) {
@@ -34037,48 +34131,6 @@ function createMattermostPermissionApi(config2) {
34037
34131
  return new MattermostPermissionApi(config2);
34038
34132
  }
34039
34133
 
34040
- // src/platform/utils.ts
34041
- function convertMarkdownToSlack(content) {
34042
- const codeBlocks = [];
34043
- const CODE_BLOCK_PLACEHOLDER = "\x00CODE_BLOCK_";
34044
- let preserved = content.replace(/```[\s\S]*?```/g, (match) => {
34045
- const index = codeBlocks.length;
34046
- codeBlocks.push(match);
34047
- return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
34048
- });
34049
- preserved = preserved.replace(/`[^`\n]+`/g, (match) => {
34050
- const index = codeBlocks.length;
34051
- codeBlocks.push(match);
34052
- return `${CODE_BLOCK_PLACEHOLDER}${index}\x00`;
34053
- });
34054
- preserved = convertMarkdownTablesToSlack(preserved);
34055
- preserved = preserved.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
34056
- preserved = preserved.replace(/\*\*([^*]+)\*\*/g, "*$1*");
34057
- preserved = preserved.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
34058
- preserved = preserved.replace(/^[-*_]{3,}\s*$/gm, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
34059
- for (let i = 0;i < codeBlocks.length; i++) {
34060
- preserved = preserved.replace(`${CODE_BLOCK_PLACEHOLDER}${i}\x00`, codeBlocks[i]);
34061
- }
34062
- return preserved;
34063
- }
34064
- function convertMarkdownTablesToSlack(content) {
34065
- const tableRegex = /^\|(.+)\|\s*\n\|[-:\s|]+\|\s*\n((?:\|.+\|\s*\n?)+)/gm;
34066
- return content.replace(tableRegex, (_match, headerLine, bodyLines) => {
34067
- const headers = headerLine.split("|").map((h) => h.trim()).filter((h) => h);
34068
- const rows = bodyLines.trim().split(`
34069
- `).map((row) => row.split("|").map((c) => c.trim()).filter((c) => c !== ""));
34070
- const formattedRows = rows.map((row) => {
34071
- const cells = row.map((cell, i) => {
34072
- const header = headers[i];
34073
- return header ? `*${header}:* ${cell}` : cell;
34074
- });
34075
- return cells.join(" \xB7 ");
34076
- });
34077
- return formattedRows.join(`
34078
- `);
34079
- });
34080
- }
34081
-
34082
34134
  // src/platform/slack/formatter.ts
34083
34135
  class SlackFormatter {
34084
34136
  formatBold(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.48.3",
3
+ "version": "0.48.5",
4
4
  "description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",