claude-threads 1.9.0 → 1.9.2

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
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.2] - 2026-04-24
9
+
10
+ ### Fixed
11
+ - **Unreadable `[object ErrorEvent]` in WebSocket error logs.** Recent Node / undici deliver a browser-style `ErrorEvent` (not a plain `Error`) to `ws.onerror`, and `` `${event}` `` stringifies that wrapper to `[object ErrorEvent]` — the original failure cause was being dropped. New `formatWebSocketError(err)` helper in `src/platform/utils.ts` pulls the first usable signal (`.message` → `.error.message` → `.type (code: .code)` → `String(err)`), wired into all five WebSocket error sites across Slack + Mattermost main clients, both MCP permission-server clients, and the UI re-emit. The Slack client's rejection and re-emitted `Error` now carry the underlying message too, instead of the opaque `"Socket Mode WebSocket error"`. (#347)
12
+ - **Worktree creation under a parent branch gave a generic "Failed to create worktree" message.** When a flat branch `test` already exists and the user requests `test/add-unit-coverage`, git refuses with `fatal: 'refs/heads/test' exists; cannot create 'refs/heads/test/add-unit-coverage'`. `parseWorktreeError` now matches this specific shape and reports `Branch <parent> already exists and blocks <nested>` with the suggestion to pick a non-nested name or delete the parent branch first. (#347)
13
+
14
+ ## [1.9.1] - 2026-04-24
15
+
16
+ ### Internals
17
+ - **Unified `Executor<TState>` contract.** New interface in `src/operations/executors/types.ts` formalizes what `MessageManager` actually relies on: `getState` / `reset` required, `handleReaction` / `serialize` optional. `BaseExecutor<T>` implements it. A new `contract.test.ts` iterates every executor and asserts the shape — catches drift when someone adds an executor without the required members. (#346)
18
+ - **Uniform `handleReaction` signature across all seven reaction executors** — `(postId, emoji, user, action, ctx) => Promise<boolean>`. Previously `TaskList`, `Subagent`, and `WorktreePrompt` had slightly different shapes. `MessageManager.handleReaction` now dispatches via a `reactionDispatchList()` table instead of an if/else chain. (#346)
19
+ - **`MessageManager.serialize()` aggregates executor state** for `SessionManager.persistSession`. The writer no longer reaches into individual executors via named getters (`getTaskListState()`, `getPendingContextPrompt()`). Legacy getters kept as `@deprecated` shims — they still have non-persistence consumers. (#346)
20
+ - **Byte-identical `sessions.json` guarantee.** New snapshot tests in `manager.test.ts` pin the full payload's field set and run the new and legacy (`CLAUDE_THREADS_SERIALIZE_V2=0`) paths through a parity assertion. No persisted-schema change on disk. (#346)
21
+ - **Rollback hatch:** `CLAUDE_THREADS_SERIALIZE_V2=0` falls back to the pre-refactor per-getter writer for one release. Removed in the next minor. (#346)
22
+
8
23
  ## [1.9.0] - 2026-04-24
9
24
 
10
25
  ### Added
package/dist/index.js CHANGED
@@ -51565,6 +51565,23 @@ function truncateAtWord(str2, maxLength) {
51565
51565
  function escapeRegExp(string) {
51566
51566
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
51567
51567
  }
51568
+ function formatWebSocketError(err) {
51569
+ if (err instanceof Error)
51570
+ return err.message;
51571
+ if (err && typeof err === "object") {
51572
+ const e = err;
51573
+ if (typeof e.message === "string" && e.message)
51574
+ return e.message;
51575
+ if (e.error instanceof Error)
51576
+ return e.error.message;
51577
+ if (typeof e.error === "string" && e.error)
51578
+ return e.error;
51579
+ if (typeof e.type === "string" && e.type) {
51580
+ return typeof e.code === "string" || typeof e.code === "number" ? `${e.type} (code: ${e.code})` : e.type;
51581
+ }
51582
+ }
51583
+ return String(err);
51584
+ }
51568
51585
  function getPlatformIcon(platformType) {
51569
51586
  switch (platformType) {
51570
51587
  case "slack":
@@ -52110,9 +52127,10 @@ class MattermostClient extends BasePlatformClient {
52110
52127
  this.onConnectionClosed();
52111
52128
  };
52112
52129
  this.ws.onerror = (event) => {
52113
- wsLogger.warn(`WebSocket error: ${event}`);
52114
- this.emit("error", event);
52115
- reject(event);
52130
+ const msg = formatWebSocketError(event);
52131
+ wsLogger.warn(`WebSocket error: ${msg}`);
52132
+ this.emit("error", new Error(`WebSocket error: ${msg}`));
52133
+ reject(new Error(`WebSocket error: ${msg}`));
52116
52134
  };
52117
52135
  });
52118
52136
  }
@@ -52540,11 +52558,12 @@ class SlackClient extends BasePlatformClient {
52540
52558
  };
52541
52559
  this.ws.onerror = (event) => {
52542
52560
  clearTimeout(connectionTimeout);
52543
- wsLogger.warn(`Socket Mode: WebSocket error: ${event}`);
52561
+ const msg = formatWebSocketError(event);
52562
+ wsLogger.warn(`Socket Mode: WebSocket error: ${msg}`);
52544
52563
  if (!this.isIntentionalDisconnect && !this.isReconnecting) {
52545
- this.emit("error", new Error("Socket Mode WebSocket error"));
52564
+ this.emit("error", new Error(`Socket Mode WebSocket error: ${msg}`));
52546
52565
  }
52547
- doReject(new Error("Socket Mode WebSocket error"));
52566
+ doReject(new Error(`Socket Mode WebSocket error: ${msg}`));
52548
52567
  };
52549
52568
  });
52550
52569
  }
@@ -53160,7 +53179,7 @@ class MattermostPermissionApi {
53160
53179
  }
53161
53180
  };
53162
53181
  ws.onerror = (event) => {
53163
- mcpLogger.error(`WebSocket error: ${event}`);
53182
+ mcpLogger.error(`WebSocket error: ${formatWebSocketError(event)}`);
53164
53183
  if (!resolved) {
53165
53184
  resolved = true;
53166
53185
  clearTimeout(timeout);
@@ -53356,7 +53375,7 @@ class SlackPermissionApi {
53356
53375
  }
53357
53376
  };
53358
53377
  ws.onerror = (event) => {
53359
- mcpLogger.error(`Socket Mode WebSocket error: ${event}`);
53378
+ mcpLogger.error(`Socket Mode WebSocket error: ${formatWebSocketError(event)}`);
53360
53379
  if (!resolved) {
53361
53380
  resolved = true;
53362
53381
  clearTimeout(timeout);
@@ -58611,7 +58630,15 @@ class TaskListExecutor extends BaseExecutor {
58611
58630
  await ctx.platform.updatePost(this.state.tasksPostId, displayContent);
58612
58631
  } catch {}
58613
58632
  }
58614
- async handleReaction(postId, emoji, action, ctx) {
58633
+ serialize() {
58634
+ return {
58635
+ postId: this.state.tasksPostId,
58636
+ content: this.state.lastTasksContent,
58637
+ isMinimized: this.state.tasksMinimized,
58638
+ isCompleted: this.state.tasksCompleted
58639
+ };
58640
+ }
58641
+ async handleReaction(postId, emoji, _user, action, ctx) {
58615
58642
  ctx.logger.debug(`TaskListExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji}, action=${action}, tasksPostId=${this.state.tasksPostId?.substring(0, 8) ?? "none"}`);
58616
58643
  if (postId !== this.state.tasksPostId) {
58617
58644
  ctx.logger.debug(`TaskListExecutor: postId does not match tasksPostId, ignoring`);
@@ -58880,7 +58907,7 @@ class SubagentExecutor extends BaseExecutor {
58880
58907
  }
58881
58908
  return false;
58882
58909
  }
58883
- async handleReaction(postId, emoji, action, ctx) {
58910
+ async handleReaction(postId, emoji, _user, action, ctx) {
58884
58911
  ctx.logger.debug(`SubagentExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji}, action=${action}`);
58885
58912
  if (!isMinimizeToggleEmoji(emoji)) {
58886
58913
  ctx.logger.debug(`SubagentExecutor: emoji ${emoji} is not minimize toggle, ignoring`);
@@ -59420,6 +59447,9 @@ class PromptExecutor extends BaseExecutor {
59420
59447
  getPendingContextPrompt() {
59421
59448
  return this.state.pendingContextPrompt;
59422
59449
  }
59450
+ serialize() {
59451
+ return this.state.pendingContextPrompt;
59452
+ }
59423
59453
  hasPendingContextPrompt() {
59424
59454
  return this.state.pendingContextPrompt !== null;
59425
59455
  }
@@ -60782,12 +60812,12 @@ class MessageManager {
60782
60812
  await this.taskListExecutor.bumpToBottom(this.getExecutorContext());
60783
60813
  }
60784
60814
  getTaskListState() {
60785
- const state = this.taskListExecutor.getState();
60815
+ return this.taskListExecutor.serialize();
60816
+ }
60817
+ serialize() {
60786
60818
  return {
60787
- postId: state.tasksPostId,
60788
- content: state.lastTasksContent,
60789
- isMinimized: state.tasksMinimized,
60790
- isCompleted: state.tasksCompleted
60819
+ taskList: this.taskListExecutor.serialize(),
60820
+ contextPrompt: this.promptExecutor.serialize()
60791
60821
  };
60792
60822
  }
60793
60823
  hydrateTaskListState(persisted) {
@@ -60880,33 +60910,27 @@ class MessageManager {
60880
60910
  getSession() {
60881
60911
  return this.session;
60882
60912
  }
60913
+ reactionDispatchList() {
60914
+ return [
60915
+ { name: "QuestionApprovalExecutor", executor: this.questionApprovalExecutor },
60916
+ { name: "MessageApprovalExecutor", executor: this.messageApprovalExecutor },
60917
+ { name: "PromptExecutor", executor: this.promptExecutor },
60918
+ { name: "BugReportExecutor", executor: this.bugReportExecutor },
60919
+ { name: "TaskListExecutor", executor: this.taskListExecutor },
60920
+ { name: "SubagentExecutor", executor: this.subagentExecutor }
60921
+ ];
60922
+ }
60883
60923
  async handleReaction(postId, emoji, user, action) {
60884
60924
  const logger = log18.forSession(this.sessionId);
60885
60925
  const ctx = this.getExecutorContext();
60886
60926
  logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji}, user=${user}, action=${action}`);
60887
- if (await this.questionApprovalExecutor.handleReaction(postId, emoji, user, action, ctx)) {
60888
- logger.debug("Reaction handled by QuestionApprovalExecutor");
60889
- return true;
60890
- }
60891
- if (await this.messageApprovalExecutor.handleReaction(postId, emoji, user, action, ctx)) {
60892
- logger.debug("Reaction handled by MessageApprovalExecutor");
60893
- return true;
60894
- }
60895
- if (await this.promptExecutor.handleReaction(postId, emoji, user, action, ctx)) {
60896
- logger.debug("Reaction handled by PromptExecutor");
60897
- return true;
60898
- }
60899
- if (await this.bugReportExecutor.handleReaction(postId, emoji, user, action, ctx)) {
60900
- logger.debug("Reaction handled by BugReportExecutor");
60901
- return true;
60902
- }
60903
- if (await this.taskListExecutor.handleReaction(postId, emoji, action, ctx)) {
60904
- logger.debug("Reaction handled by TaskListExecutor");
60905
- return true;
60906
- }
60907
- if (await this.subagentExecutor.handleReaction(postId, emoji, action, ctx)) {
60908
- logger.debug("Reaction handled by SubagentExecutor");
60909
- return true;
60927
+ for (const { name, executor } of this.reactionDispatchList()) {
60928
+ if (!executor.handleReaction)
60929
+ continue;
60930
+ if (await executor.handleReaction(postId, emoji, user, action, ctx)) {
60931
+ logger.debug(`Reaction handled by ${name}`);
60932
+ return true;
60933
+ }
60910
60934
  }
60911
60935
  logger.debug("Reaction not handled by any executor");
60912
60936
  return false;
@@ -66313,6 +66337,15 @@ function parseWorktreeError(error) {
66313
66337
  suggestion: "Try a different branch name, or use `!worktree list` to see existing worktrees"
66314
66338
  };
66315
66339
  }
66340
+ const parentBlocksMatch = message.match(/'refs\/heads\/([^']+)' exists; cannot create '(?:refs\/heads\/)?([^']+)'/i);
66341
+ if (parentBlocksMatch) {
66342
+ const parent = parentBlocksMatch[1];
66343
+ const nested = parentBlocksMatch[2];
66344
+ return {
66345
+ summary: `Branch ${parent} already exists and blocks ${nested}`,
66346
+ suggestion: `Pick a name that does not start with ${parent}/, or delete the existing ${parent} branch first.`
66347
+ };
66348
+ }
66316
66349
  if (lowerMessage.includes("already exists")) {
66317
66350
  return {
66318
66351
  summary: "A worktree or branch with this name already exists",
@@ -68910,19 +68943,29 @@ class SessionManager extends EventEmitter4 {
68910
68943
  this.stopTyping(session);
68911
68944
  }
68912
68945
  persistSession(session) {
68913
- let persistedContextPrompt;
68914
- const contextPromptState = session.messageManager?.getPendingContextPrompt();
68915
- if (contextPromptState) {
68916
- persistedContextPrompt = {
68917
- postId: contextPromptState.postId,
68918
- queuedPrompt: contextPromptState.queuedPrompt,
68919
- queuedFiles: contextPromptState.queuedFiles,
68920
- threadMessageCount: contextPromptState.threadMessageCount,
68921
- createdAt: contextPromptState.createdAt,
68922
- availableOptions: contextPromptState.availableOptions
68923
- };
68946
+ const useSerializeV2 = process.env.CLAUDE_THREADS_SERIALIZE_V2 !== "0";
68947
+ let taskListSnapshot;
68948
+ let contextPromptSnapshot;
68949
+ if (useSerializeV2 && session.messageManager) {
68950
+ const serialized = session.messageManager.serialize();
68951
+ taskListSnapshot = serialized.taskList;
68952
+ if (serialized.contextPrompt) {
68953
+ contextPromptSnapshot = serialized.contextPrompt;
68954
+ }
68955
+ } else {
68956
+ const legacyPrompt = session.messageManager?.getPendingContextPrompt();
68957
+ if (legacyPrompt) {
68958
+ contextPromptSnapshot = {
68959
+ postId: legacyPrompt.postId,
68960
+ queuedPrompt: legacyPrompt.queuedPrompt,
68961
+ queuedFiles: legacyPrompt.queuedFiles,
68962
+ threadMessageCount: legacyPrompt.threadMessageCount,
68963
+ createdAt: legacyPrompt.createdAt,
68964
+ availableOptions: legacyPrompt.availableOptions
68965
+ };
68966
+ }
68967
+ taskListSnapshot = session.messageManager?.getTaskListState();
68924
68968
  }
68925
- const taskState = session.messageManager?.getTaskListState();
68926
68969
  const state = {
68927
68970
  platformId: session.platformId,
68928
68971
  threadId: session.threadId,
@@ -68937,10 +68980,10 @@ class SessionManager extends EventEmitter4 {
68937
68980
  sessionAllowedUsers: [...session.sessionAllowedUsers],
68938
68981
  forceInteractivePermissions: session.forceInteractivePermissions,
68939
68982
  sessionStartPostId: session.sessionStartPostId,
68940
- tasksPostId: taskState?.postId ?? null,
68941
- lastTasksContent: taskState?.content ?? null,
68942
- tasksCompleted: taskState?.isCompleted ?? false,
68943
- tasksMinimized: taskState?.isMinimized ?? false,
68983
+ tasksPostId: taskListSnapshot?.postId ?? null,
68984
+ lastTasksContent: taskListSnapshot?.content ?? null,
68985
+ tasksCompleted: taskListSnapshot?.isCompleted ?? false,
68986
+ tasksMinimized: taskListSnapshot?.isMinimized ?? false,
68944
68987
  worktreeInfo: session.worktreeInfo,
68945
68988
  isWorktreeOwner: session.isWorktreeOwner,
68946
68989
  pendingWorktreePrompt: session.pendingWorktreePrompt,
@@ -68948,7 +68991,7 @@ class SessionManager extends EventEmitter4 {
68948
68991
  queuedPrompt: session.queuedPrompt,
68949
68992
  queuedFiles: session.queuedFiles,
68950
68993
  firstPrompt: session.firstPrompt,
68951
- pendingContextPrompt: persistedContextPrompt,
68994
+ pendingContextPrompt: contextPromptSnapshot,
68952
68995
  needsContextPromptOnNextMessage: session.needsContextPromptOnNextMessage,
68953
68996
  lifecyclePostId: session.lifecyclePostId,
68954
68997
  isPaused: session.lifecycle.state === "paused" || session.lifecycle.state === "interrupted",
@@ -80089,7 +80132,8 @@ function wirePlatformEvents(platformId, client, session, ui) {
80089
80132
  ui.setPlatformStatus(platformId, { reconnecting: true, reconnectAttempts: attempt });
80090
80133
  });
80091
80134
  client.on("error", (e) => {
80092
- ui.addLog({ level: "error", component: platformId, message: String(e) });
80135
+ const message = e instanceof Error ? e.message : String(e);
80136
+ ui.addLog({ level: "error", component: platformId, message });
80093
80137
  });
80094
80138
  }
80095
80139
  program.name("claude-threads").version(VERSION).description("Share Claude Code sessions in Mattermost").option("--url <url>", "Mattermost server URL").option("--token <token>", "Mattermost bot token").option("--channel <id>", "Mattermost channel ID").option("--bot-name <name>", "Bot mention name (default: claude-code)").option("--allowed-users <users>", "Comma-separated allowed usernames").option("--permission-mode <mode>", "Permission mode: default | auto | bypass (default: from config)").option("--skip-permissions", "[deprecated] Alias for --permission-mode bypass").option("--no-skip-permissions", "[deprecated] Alias for --permission-mode default").option("--chrome", "Enable Claude in Chrome integration").option("--no-chrome", "Disable Claude in Chrome integration").option("--worktree-mode <mode>", "Git worktree mode: off, prompt, require (default: prompt)").option("--keep-alive", "Enable system sleep prevention (default: enabled)").option("--no-keep-alive", "Disable system sleep prevention").option("--setup", "Run interactive setup wizard (reconfigure existing settings)").option("--debug", "Enable debug logging").option("--skip-version-check", "Skip Claude CLI version compatibility check").option("--auto-restart", "Enable auto-restart on updates (default when autoUpdate enabled)").option("--no-auto-restart", "Disable auto-restart on updates").option("--headless", "Run without interactive UI (logs to stdout)").parse();
@@ -50456,6 +50456,23 @@ class BaseExecutor {
50456
50456
  }
50457
50457
  }
50458
50458
  // src/platform/utils.ts
50459
+ function formatWebSocketError(err) {
50460
+ if (err instanceof Error)
50461
+ return err.message;
50462
+ if (err && typeof err === "object") {
50463
+ const e = err;
50464
+ if (typeof e.message === "string" && e.message)
50465
+ return e.message;
50466
+ if (e.error instanceof Error)
50467
+ return e.error.message;
50468
+ if (typeof e.error === "string" && e.error)
50469
+ return e.error;
50470
+ if (typeof e.type === "string" && e.type) {
50471
+ return typeof e.code === "string" || typeof e.code === "number" ? `${e.type} (code: ${e.code})` : e.type;
50472
+ }
50473
+ }
50474
+ return String(err);
50475
+ }
50459
50476
  function truncateMessageSafely(message, maxLength, truncationIndicator = "... (truncated)") {
50460
50477
  if (message.length <= maxLength)
50461
50478
  return message;
@@ -51175,7 +51192,15 @@ class TaskListExecutor extends BaseExecutor {
51175
51192
  await ctx.platform.updatePost(this.state.tasksPostId, displayContent);
51176
51193
  } catch {}
51177
51194
  }
51178
- async handleReaction(postId, emoji4, action, ctx) {
51195
+ serialize() {
51196
+ return {
51197
+ postId: this.state.tasksPostId,
51198
+ content: this.state.lastTasksContent,
51199
+ isMinimized: this.state.tasksMinimized,
51200
+ isCompleted: this.state.tasksCompleted
51201
+ };
51202
+ }
51203
+ async handleReaction(postId, emoji4, _user, action, ctx) {
51179
51204
  ctx.logger.debug(`TaskListExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji4}, action=${action}, tasksPostId=${this.state.tasksPostId?.substring(0, 8) ?? "none"}`);
51180
51205
  if (postId !== this.state.tasksPostId) {
51181
51206
  ctx.logger.debug(`TaskListExecutor: postId does not match tasksPostId, ignoring`);
@@ -51444,7 +51469,7 @@ class SubagentExecutor extends BaseExecutor {
51444
51469
  }
51445
51470
  return false;
51446
51471
  }
51447
- async handleReaction(postId, emoji4, action, ctx) {
51472
+ async handleReaction(postId, emoji4, _user, action, ctx) {
51448
51473
  ctx.logger.debug(`SubagentExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji4}, action=${action}`);
51449
51474
  if (!isMinimizeToggleEmoji(emoji4)) {
51450
51475
  ctx.logger.debug(`SubagentExecutor: emoji ${emoji4} is not minimize toggle, ignoring`);
@@ -51984,6 +52009,9 @@ class PromptExecutor extends BaseExecutor {
51984
52009
  getPendingContextPrompt() {
51985
52010
  return this.state.pendingContextPrompt;
51986
52011
  }
52012
+ serialize() {
52013
+ return this.state.pendingContextPrompt;
52014
+ }
51987
52015
  hasPendingContextPrompt() {
51988
52016
  return this.state.pendingContextPrompt !== null;
51989
52017
  }
@@ -52734,12 +52762,12 @@ class MessageManager {
52734
52762
  await this.taskListExecutor.bumpToBottom(this.getExecutorContext());
52735
52763
  }
52736
52764
  getTaskListState() {
52737
- const state = this.taskListExecutor.getState();
52765
+ return this.taskListExecutor.serialize();
52766
+ }
52767
+ serialize() {
52738
52768
  return {
52739
- postId: state.tasksPostId,
52740
- content: state.lastTasksContent,
52741
- isMinimized: state.tasksMinimized,
52742
- isCompleted: state.tasksCompleted
52769
+ taskList: this.taskListExecutor.serialize(),
52770
+ contextPrompt: this.promptExecutor.serialize()
52743
52771
  };
52744
52772
  }
52745
52773
  hydrateTaskListState(persisted) {
@@ -52832,33 +52860,27 @@ class MessageManager {
52832
52860
  getSession() {
52833
52861
  return this.session;
52834
52862
  }
52863
+ reactionDispatchList() {
52864
+ return [
52865
+ { name: "QuestionApprovalExecutor", executor: this.questionApprovalExecutor },
52866
+ { name: "MessageApprovalExecutor", executor: this.messageApprovalExecutor },
52867
+ { name: "PromptExecutor", executor: this.promptExecutor },
52868
+ { name: "BugReportExecutor", executor: this.bugReportExecutor },
52869
+ { name: "TaskListExecutor", executor: this.taskListExecutor },
52870
+ { name: "SubagentExecutor", executor: this.subagentExecutor }
52871
+ ];
52872
+ }
52835
52873
  async handleReaction(postId, emoji4, user, action) {
52836
52874
  const logger = log3.forSession(this.sessionId);
52837
52875
  const ctx = this.getExecutorContext();
52838
52876
  logger.debug(`Routing reaction: postId=${postId}, emoji=${emoji4}, user=${user}, action=${action}`);
52839
- if (await this.questionApprovalExecutor.handleReaction(postId, emoji4, user, action, ctx)) {
52840
- logger.debug("Reaction handled by QuestionApprovalExecutor");
52841
- return true;
52842
- }
52843
- if (await this.messageApprovalExecutor.handleReaction(postId, emoji4, user, action, ctx)) {
52844
- logger.debug("Reaction handled by MessageApprovalExecutor");
52845
- return true;
52846
- }
52847
- if (await this.promptExecutor.handleReaction(postId, emoji4, user, action, ctx)) {
52848
- logger.debug("Reaction handled by PromptExecutor");
52849
- return true;
52850
- }
52851
- if (await this.bugReportExecutor.handleReaction(postId, emoji4, user, action, ctx)) {
52852
- logger.debug("Reaction handled by BugReportExecutor");
52853
- return true;
52854
- }
52855
- if (await this.taskListExecutor.handleReaction(postId, emoji4, action, ctx)) {
52856
- logger.debug("Reaction handled by TaskListExecutor");
52857
- return true;
52858
- }
52859
- if (await this.subagentExecutor.handleReaction(postId, emoji4, action, ctx)) {
52860
- logger.debug("Reaction handled by SubagentExecutor");
52861
- return true;
52877
+ for (const { name, executor } of this.reactionDispatchList()) {
52878
+ if (!executor.handleReaction)
52879
+ continue;
52880
+ if (await executor.handleReaction(postId, emoji4, user, action, ctx)) {
52881
+ logger.debug(`Reaction handled by ${name}`);
52882
+ return true;
52883
+ }
52862
52884
  }
52863
52885
  logger.debug("Reaction not handled by any executor");
52864
52886
  return false;
@@ -57482,7 +57504,7 @@ class MattermostPermissionApi {
57482
57504
  }
57483
57505
  };
57484
57506
  ws.onerror = (event) => {
57485
- mcpLogger.error(`WebSocket error: ${event}`);
57507
+ mcpLogger.error(`WebSocket error: ${formatWebSocketError(event)}`);
57486
57508
  if (!resolved) {
57487
57509
  resolved = true;
57488
57510
  clearTimeout(timeout);
@@ -57749,7 +57771,7 @@ class SlackPermissionApi {
57749
57771
  }
57750
57772
  };
57751
57773
  ws.onerror = (event) => {
57752
- mcpLogger.error(`Socket Mode WebSocket error: ${event}`);
57774
+ mcpLogger.error(`Socket Mode WebSocket error: ${formatWebSocketError(event)}`);
57753
57775
  if (!resolved) {
57754
57776
  resolved = true;
57755
57777
  clearTimeout(timeout);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
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",