claude-threads 1.0.2 → 1.0.3

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,12 @@ 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.0.3] - 2026-01-13
9
+
10
+ ### Fixed
11
+ - **WebSocket reconnection after long idle** - Improved reconnection reliability with forceful cleanup of stale connections, automatic retry on failure, and more compact UI (#206)
12
+ - **Metadata suggestion retry logic** - Added retry logic for title/description/tags fetching on session start with up to 2 retries (#207)
13
+
8
14
  ## [1.0.2] - 2026-01-13
9
15
 
10
16
  ### Fixed
package/dist/index.js CHANGED
@@ -47158,19 +47158,36 @@ class MattermostClient extends EventEmitter {
47158
47158
  }
47159
47159
  }
47160
47160
  }
47161
+ cleanupWebSocket() {
47162
+ this.stopHeartbeat();
47163
+ if (this.ws) {
47164
+ this.ws.onopen = null;
47165
+ this.ws.onmessage = null;
47166
+ this.ws.onclose = null;
47167
+ this.ws.onerror = null;
47168
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
47169
+ try {
47170
+ this.ws.close();
47171
+ } catch {}
47172
+ }
47173
+ this.ws = null;
47174
+ }
47175
+ }
47161
47176
  scheduleReconnect() {
47162
47177
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
47163
47178
  log.error("Max reconnection attempts reached");
47164
47179
  return;
47165
47180
  }
47181
+ this.cleanupWebSocket();
47166
47182
  this.isReconnecting = true;
47167
47183
  this.reconnectAttempts++;
47168
47184
  const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
47169
- log.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
47185
+ wsLogger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
47170
47186
  this.emit("reconnecting", this.reconnectAttempts);
47171
47187
  setTimeout(() => {
47172
47188
  this.connect().catch((err) => {
47173
- log.error(`Reconnection failed: ${err}`);
47189
+ wsLogger.error(`Reconnection failed: ${err}`);
47190
+ this.scheduleReconnect();
47174
47191
  });
47175
47192
  }, delay);
47176
47193
  }
@@ -47689,6 +47706,21 @@ class SlackClient extends EventEmitter2 {
47689
47706
  });
47690
47707
  }
47691
47708
  }
47709
+ cleanupWebSocket() {
47710
+ this.stopHeartbeat();
47711
+ if (this.ws) {
47712
+ this.ws.onopen = null;
47713
+ this.ws.onmessage = null;
47714
+ this.ws.onclose = null;
47715
+ this.ws.onerror = null;
47716
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
47717
+ try {
47718
+ this.ws.close();
47719
+ } catch {}
47720
+ }
47721
+ this.ws = null;
47722
+ }
47723
+ }
47692
47724
  scheduleReconnect() {
47693
47725
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
47694
47726
  log2.error("Max reconnection attempts reached");
@@ -47698,10 +47730,11 @@ class SlackClient extends EventEmitter2 {
47698
47730
  clearTimeout(this.reconnectTimeout);
47699
47731
  this.reconnectTimeout = null;
47700
47732
  }
47733
+ this.cleanupWebSocket();
47701
47734
  this.isReconnecting = true;
47702
47735
  this.reconnectAttempts++;
47703
47736
  const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
47704
- log2.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
47737
+ wsLogger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
47705
47738
  this.emit("reconnecting", this.reconnectAttempts);
47706
47739
  this.reconnectTimeout = setTimeout(() => {
47707
47740
  this.reconnectTimeout = null;
@@ -47710,7 +47743,8 @@ class SlackClient extends EventEmitter2 {
47710
47743
  return;
47711
47744
  }
47712
47745
  this.connect().catch((err) => {
47713
- log2.error(`Reconnection failed: ${err}`);
47746
+ wsLogger.error(`Reconnection failed: ${err}`);
47747
+ this.scheduleReconnect();
47714
47748
  });
47715
47749
  }, delay);
47716
47750
  }
@@ -60971,35 +61005,76 @@ function createMessageManager(session, ctx) {
60971
61005
  });
60972
61006
  return messageManager;
60973
61007
  }
61008
+ var METADATA_RETRY_DELAY_MS = 2000;
61009
+ var METADATA_MAX_RETRIES = 2;
61010
+ async function attemptMetadataFetch(session, prompt, ctx, attempt = 1, options = {}) {
61011
+ const sessionId = session.sessionId;
61012
+ const suggestMetadataFn = options.suggestMetadata ?? suggestSessionMetadata;
61013
+ const suggestTagsFn = options.suggestTags ?? suggestSessionTags;
61014
+ const [metadata, tags] = await Promise.all([
61015
+ suggestMetadataFn(prompt),
61016
+ suggestTagsFn(prompt)
61017
+ ]);
61018
+ const currentSession = ctx.state.sessions.get(sessionId);
61019
+ if (!currentSession) {
61020
+ sessionLog6(session).debug("Session gone before metadata suggestions completed");
61021
+ return { success: false, metadataSet: false, tagsSet: false };
61022
+ }
61023
+ let metadataSet = false;
61024
+ let tagsSet = false;
61025
+ let updated = false;
61026
+ if (metadata && !currentSession.sessionTitle) {
61027
+ currentSession.sessionTitle = metadata.title;
61028
+ currentSession.sessionDescription = metadata.description;
61029
+ sessionLog6(currentSession).debug(`Set title: "${metadata.title}" (attempt ${attempt})`);
61030
+ metadataSet = true;
61031
+ updated = true;
61032
+ } else if (currentSession.sessionTitle) {
61033
+ metadataSet = true;
61034
+ }
61035
+ if (tags.length > 0 && (!currentSession.sessionTags || currentSession.sessionTags.length === 0)) {
61036
+ currentSession.sessionTags = tags;
61037
+ sessionLog6(currentSession).debug(`Set tags: ${tags.join(", ")} (attempt ${attempt})`);
61038
+ tagsSet = true;
61039
+ updated = true;
61040
+ } else if (currentSession.sessionTags && currentSession.sessionTags.length > 0) {
61041
+ tagsSet = true;
61042
+ }
61043
+ if (updated) {
61044
+ ctx.ops.persistSession(currentSession);
61045
+ await ctx.ops.updateStickyMessage();
61046
+ await ctx.ops.updateSessionHeader(currentSession);
61047
+ }
61048
+ return { success: metadataSet && tagsSet, metadataSet, tagsSet };
61049
+ }
60974
61050
  function fireMetadataSuggestions(session, prompt, ctx) {
60975
61051
  (async () => {
60976
61052
  try {
60977
- const sessionId = session.sessionId;
60978
- const [metadata, tags] = await Promise.all([
60979
- suggestSessionMetadata(prompt),
60980
- suggestSessionTags(prompt)
60981
- ]);
60982
- const currentSession = ctx.state.sessions.get(sessionId);
60983
- if (!currentSession) {
60984
- sessionLog6(session).debug("Session gone before metadata suggestions completed");
60985
- return;
60986
- }
60987
- let updated = false;
60988
- if (metadata && !currentSession.sessionTitle) {
60989
- currentSession.sessionTitle = metadata.title;
60990
- currentSession.sessionDescription = metadata.description;
60991
- sessionLog6(currentSession).debug(`Set title: "${metadata.title}"`);
60992
- updated = true;
60993
- }
60994
- if (tags.length > 0 && (!currentSession.sessionTags || currentSession.sessionTags.length === 0)) {
60995
- currentSession.sessionTags = tags;
60996
- sessionLog6(currentSession).debug(`Set tags: ${tags.join(", ")}`);
60997
- updated = true;
60998
- }
60999
- if (updated) {
61000
- ctx.ops.persistSession(currentSession);
61001
- await ctx.ops.updateStickyMessage();
61002
- await ctx.ops.updateSessionHeader(currentSession);
61053
+ let result = await attemptMetadataFetch(session, prompt, ctx, 1);
61054
+ let attempt = 1;
61055
+ while (!result.success && attempt < METADATA_MAX_RETRIES + 1) {
61056
+ attempt++;
61057
+ const currentSession = ctx.state.sessions.get(session.sessionId);
61058
+ if (!currentSession) {
61059
+ sessionLog6(session).debug("Session gone, stopping metadata retries");
61060
+ return;
61061
+ }
61062
+ const missing = [];
61063
+ if (!result.metadataSet)
61064
+ missing.push("title/description");
61065
+ if (!result.tagsSet)
61066
+ missing.push("tags");
61067
+ sessionLog6(session).debug(`Retrying metadata fetch for ${missing.join(", ")} (attempt ${attempt}/${METADATA_MAX_RETRIES + 1})`);
61068
+ await new Promise((resolve6) => setTimeout(resolve6, METADATA_RETRY_DELAY_MS));
61069
+ result = await attemptMetadataFetch(session, prompt, ctx, attempt);
61070
+ }
61071
+ if (!result.success) {
61072
+ const missing = [];
61073
+ if (!result.metadataSet)
61074
+ missing.push("title/description");
61075
+ if (!result.tagsSet)
61076
+ missing.push("tags");
61077
+ sessionLog6(session).debug(`Metadata fetch incomplete after ${attempt} attempts: missing ${missing.join(", ")}`);
61003
61078
  }
61004
61079
  } catch (err) {
61005
61080
  sessionLog6(session).debug(`Metadata suggestion error: ${err}`);
@@ -71118,9 +71193,8 @@ function Platforms({ platforms }) {
71118
71193
  !platform2.enabled ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71119
71194
  dimColor: true,
71120
71195
  children: "\u25CB"
71121
- }, undefined, false, undefined, this) : platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71122
- color: "yellow",
71123
- children: "\u25CC"
71196
+ }, undefined, false, undefined, this) : platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Spinner2, {
71197
+ type: "dots"
71124
71198
  }, undefined, false, undefined, this) : platform2.connected ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71125
71199
  color: "green",
71126
71200
  children: "\u25CF"
@@ -71136,30 +71210,20 @@ function Platforms({ platforms }) {
71136
71210
  platform2.botName
71137
71211
  ]
71138
71212
  }, undefined, true, undefined, this),
71139
- platform2.reconnecting ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(jsx_dev_runtime3.Fragment, {
71140
- children: [
71141
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Spinner2, {
71142
- type: "dots"
71143
- }, undefined, false, undefined, this),
71144
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71145
- color: "yellow",
71146
- children: [
71147
- "reconnecting (",
71148
- platform2.reconnectAttempts,
71149
- ")"
71150
- ]
71151
- }, undefined, true, undefined, this)
71152
- ]
71153
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(jsx_dev_runtime3.Fragment, {
71213
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71214
+ dimColor: true,
71215
+ children: "on"
71216
+ }, undefined, false, undefined, this),
71217
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71218
+ dimColor: !platform2.enabled,
71219
+ children: platform2.displayName
71220
+ }, undefined, false, undefined, this),
71221
+ platform2.reconnecting && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71222
+ dimColor: true,
71154
71223
  children: [
71155
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71156
- dimColor: true,
71157
- children: "on"
71158
- }, undefined, false, undefined, this),
71159
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
71160
- dimColor: !platform2.enabled,
71161
- children: platform2.displayName
71162
- }, undefined, false, undefined, this)
71224
+ "(retry ",
71225
+ platform2.reconnectAttempts,
71226
+ ")"
71163
71227
  ]
71164
71228
  }, undefined, true, undefined, this)
71165
71229
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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",