claude-threads 0.37.0 β†’ 0.38.0

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,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.38.0] - 2026-01-06
11
+
12
+ ### Added
13
+ - **Documentation reorganization** - Moved detailed setup guides to `docs/` folder:
14
+ - `docs/CONFIGURATION.md` - Multi-platform configuration reference
15
+ - `docs/MATTERMOST_SETUP.md` - Mattermost setup guide
16
+ - `docs/SLACK_SETUP.md` - Slack setup guide
17
+
18
+ ### Fixed
19
+ - **Slack link previews disabled** - Sticky messages and task posts no longer show link unfurls on Slack
20
+ - **Jump-to-bottom links include bot posts** - Links now correctly scroll to the latest message including bot's own posts
21
+ - **Flaky integration tests** - Fixed timing issues in multi-user and session limit tests
22
+
10
23
  ## [0.37.0] - 2026-01-06
11
24
 
12
25
  ### Added
package/README.md CHANGED
@@ -9,549 +9,125 @@
9
9
  [![npm version](https://img.shields.io/npm/v/claude-threads.svg)](https://www.npmjs.com/package/claude-threads)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/claude-threads.svg)](https://www.npmjs.com/package/claude-threads)
11
11
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
12
- [![Bun](https://img.shields.io/badge/Bun-%3E%3D1.2.21-black.svg)](https://bun.sh/)
13
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue.svg)](https://www.typescriptlang.org/)
14
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
15
12
 
16
13
  **Bring Claude Code to your team.** Run Claude Code on your machine, share it live in Mattermost or Slack. Colleagues can watch, collaborate, and run their own sessionsβ€”all from chat.
17
14
 
18
- > πŸ’‘ *Think of it as screen-sharing for AI pair programming, but everyone can type.*
15
+ > *Think of it as screen-sharing for AI pair programming, but everyone can type.*
19
16
 
20
17
  ## Features
21
18
 
22
- - **Real-time streaming** - Claude's responses stream live to Mattermost/Slack
23
- - **Multi-platform support** - Connect to multiple Mattermost and Slack workspaces
24
- - **Multiple concurrent sessions** - Each thread gets its own Claude session
25
- - **Session persistence** - Sessions survive bot restarts and resume automatically
26
- - **Session collaboration** - Invite others to participate in your session
19
+ - **Real-time streaming** - Claude's responses stream live to chat
20
+ - **Multi-platform** - Connect to multiple Mattermost and Slack workspaces
21
+ - **Concurrent sessions** - Each thread gets its own Claude session
22
+ - **Session persistence** - Sessions survive bot restarts
23
+ - **Collaboration** - Invite others to participate in your session
27
24
  - **Interactive permissions** - Approve Claude's actions via emoji reactions
28
- - **Plan approval** - Review and approve Claude's plans before execution
29
- - **Task tracking** - Live todo list updates as Claude works (collapsible)
30
- - **Code diffs** - See exactly what Claude is changing
25
+ - **Git worktrees** - Isolate changes in separate branches
31
26
  - **Image attachments** - Attach images for Claude to analyze
32
- - **Thread context** - Start mid-thread and include previous messages as context
33
- - **Git worktrees** - Isolate changes in separate branches/directories
34
27
  - **Chrome automation** - Control Chrome browser for web tasks
35
- - **Keep-alive** - Prevents system sleep while sessions are active
36
28
 
37
- ## How it works
38
-
39
- ```mermaid
40
- flowchart TB
41
- subgraph local["Your Local Machine"]
42
- cli["Claude Code CLI<br/>(subprocess)"]
43
- mm["claude-threads<br/>(this service)"]
44
- cli <-->|"stdio"| mm
45
- end
46
-
47
- subgraph server["Chat Platform (Mattermost/Slack)"]
48
- bot["Bot Account<br/>@claude-code"]
49
- channel["Channel<br/>#claude-sessions"]
50
- bot <--> channel
51
- end
52
-
53
- mm -->|"WebSocket + REST API<br/>(outbound only)"| server
54
- ```
55
-
56
- Runs entirely on your machine - only **outbound** connections to your chat platform. No port forwarding needed!
29
+ ## Quick Start
57
30
 
58
- ## Prerequisites
31
+ ### Prerequisites
59
32
 
60
33
  1. **Claude Code CLI** installed and authenticated (`claude --version`)
61
- 2. **Bun 1.2.21+** (`bun --version`) - [Install Bun](https://bun.sh/)
62
- 3. **Chat platform bot account:**
63
- - **Mattermost**: Bot account with personal access token
64
- - **Slack**: Slack app with Socket Mode enabled (see [Slack Setup](#for-slack-admins))
65
-
66
- ## Quick Start
34
+ 2. **Bun 1.2.21+** - [Install Bun](https://bun.sh/)
35
+ 3. **Bot account** - [Mattermost](docs/MATTERMOST_SETUP.md) or [Slack](docs/SLACK_SETUP.md)
67
36
 
68
- ### 1. Install
37
+ ### Install & Run
69
38
 
70
39
  ```bash
71
40
  bun install -g claude-threads
72
- ```
73
-
74
- ### 2. Run
75
-
76
- ```bash
77
41
  cd /your/project
78
42
  claude-threads
79
43
  ```
80
44
 
81
- On first run, an interactive setup wizard guides you through configuration:
82
-
83
- ```
84
- claude-threads setup
85
- ─────────────────────────────────
86
-
87
- Welcome! Let's configure claude-threads.
88
-
89
- ? Default working directory: /home/user/projects
90
- ? Enable Chrome integration? No
91
- ? Git worktree mode: Prompt
92
-
93
- Now let's add your platform connections.
94
-
95
- ? First platform: Slack
96
- ? Platform ID: slack-team
97
- ? Display name: Engineering Team
98
-
99
- Slack setup (requires Socket Mode):
100
- Create app at: api.slack.com/apps
45
+ On first run, an interactive setup wizard guides you through configuration.
101
46
 
102
- ? Bot token (xoxb-...): ********
103
- ? App token (xapp-...): ********
104
- ? Channel ID: C0123456789
105
- ? Bot mention name: claude
106
- ? Allowed usernames (optional): alice,bob
47
+ ### Use
107
48
 
108
- βœ“ Added Slack
109
-
110
- ? Add another platform? No
111
-
112
- βœ“ Configuration saved!
113
- ~/.config/claude-threads/config.yaml
114
-
115
- Configured 1 platform(s):
116
- β€’ Engineering Team (slack)
117
-
118
- Starting claude-threads...
119
- ```
120
-
121
- ### 3. Use
122
-
123
- In your chat platform, mention the bot:
49
+ Mention the bot in your chat:
124
50
 
125
51
  ```
126
52
  @claude help me fix the bug in src/auth.ts
127
53
  ```
128
54
 
129
- ## CLI Options
130
-
131
- ```bash
132
- claude-threads [options]
133
-
134
- Options:
135
- --url <url> Mattermost server URL
136
- --token <token> Bot token
137
- --channel <id> Channel ID
138
- --bot-name <name> Bot mention name (default: claude-code)
139
- --allowed-users <list> Comma-separated allowed usernames
140
- --skip-permissions Skip permission prompts (auto-approve)
141
- --no-skip-permissions Enable permission prompts (override env)
142
- --chrome Enable Chrome integration
143
- --no-chrome Disable Chrome integration
144
- --worktree-mode <mode> Git worktree mode: off, prompt, require
145
- --setup Re-run setup wizard (reconfigure settings)
146
- --debug Enable debug logging
147
- --version Show version
148
- --help Show help
149
- ```
150
-
151
- CLI options override environment variables.
152
-
153
55
  ## Session Commands
154
56
 
155
- Type `!help` in any session thread to see available commands:
57
+ Type `!help` in any session thread:
156
58
 
157
59
  | Command | Description |
158
60
  |:--------|:------------|
159
61
  | `!help` | Show available commands |
160
- | `!release-notes` | Show release notes for current version |
161
- | `!context` | Show context usage (accurate % from Claude's status line) |
162
- | `!cost` | Show token usage and cost for this session |
62
+ | `!context` | Show context usage |
63
+ | `!cost` | Show token usage and cost |
163
64
  | `!compact` | Compress context to free up space |
164
- | `!cd <path>` | Change working directory (restarts Claude) |
65
+ | `!cd <path>` | Change working directory |
165
66
  | `!worktree <branch>` | Create and switch to a git worktree |
166
67
  | `!invite @user` | Invite a user to this session |
167
68
  | `!kick @user` | Remove an invited user |
168
- | `!permissions interactive` | Enable interactive permissions |
169
- | `!escape` | Interrupt current task (keeps session active) |
69
+ | `!escape` | Interrupt current task |
170
70
  | `!stop` | Stop this session |
171
- | `!kill` | Emergency shutdown (kills ALL sessions, exits bot) |
172
-
173
- > **Note:** Commands use `!` prefix instead of `/` to avoid conflicts with platform slash commands.
174
-
175
- ## Session Collaboration
176
-
177
- ### Invite Users
178
-
179
- Session owners can temporarily allow others to participate:
180
-
181
- ```
182
- !invite @colleague
183
- ```
184
71
 
185
- The colleague can now send messages in this session thread. The bot validates that the user exists before inviting.
72
+ ## Interactive Controls
186
73
 
187
- ### Kick Users
74
+ **Permission approval** - When Claude wants to execute a tool:
75
+ - πŸ‘ Allow this action
76
+ - βœ… Allow all future actions
77
+ - πŸ‘Ž Deny
188
78
 
189
- Remove an invited user from the session:
79
+ **Plan approval** - When Claude creates a plan:
80
+ - πŸ‘ Approve and start
81
+ - πŸ‘Ž Request changes
190
82
 
191
- ```
192
- !kick @colleague
193
- ```
83
+ **Questions** - React with 1️⃣ 2️⃣ 3️⃣ 4️⃣ to answer multiple choice
194
84
 
195
- The bot validates that the user exists before attempting to kick them.
85
+ **Cancel session** - Type `!stop` or react with ❌
196
86
 
197
- ### Message Approval
198
-
199
- When an unauthorized user sends a message in a session thread, the owner sees an approval prompt:
87
+ ## Collaboration
200
88
 
201
89
  ```
202
- πŸ”’ @unauthorized-user wants to send a message:
203
- > Can you also add error handling?
204
-
205
- React πŸ‘ to allow this message, βœ… to invite them to the session, πŸ‘Ž to deny
90
+ !invite @colleague # Let them participate
91
+ !kick @colleague # Remove access
206
92
  ```
207
93
 
208
- ### Side Conversations
209
-
210
- Messages starting with `@someone-else` are ignored by the bot, allowing side conversations in the thread without triggering Claude.
211
-
212
- ### Downgrade Permissions
213
-
214
- If the bot is running with `--skip-permissions` (auto mode), you can enable interactive permissions for a specific session:
215
-
216
- ```
217
- !permissions interactive
218
- ```
219
-
220
- This allows collaboration by requiring approval for Claude's actions. Note: you can only downgrade (auto β†’ interactive), not upgrade - this ensures security.
94
+ Unauthorized users can request message approval from the session owner.
221
95
 
222
96
  ## Git Worktrees
223
97
 
224
- When working on a task that requires code changes, Claude can work in an isolated git worktree. This keeps your main branch clean while Claude works on a feature branch in a separate directory.
225
-
226
- ### Starting a Session with a Worktree
227
-
228
- Specify a branch when starting:
98
+ Keep your main branch clean while Claude works on features:
229
99
 
230
100
  ```
231
101
  @claude on branch feature/add-auth implement user authentication
232
102
  ```
233
103
 
234
- Or use the worktree command:
235
-
236
- ```
237
- @claude !worktree feature/add-auth implement user authentication
238
- ```
239
-
240
- ### Worktree Commands
241
-
242
- | Command | Description |
243
- |:--------|:------------|
244
- | `!worktree <branch>` | Create worktree and switch to it |
245
- | `!worktree list` | List all worktrees for this repo |
246
- | `!worktree switch <branch>` | Switch to an existing worktree |
247
- | `!worktree remove <branch>` | Remove a worktree |
248
- | `!worktree off` | Disable worktree prompts for this session |
249
-
250
- ### How It Works
251
-
252
- 1. Creates a new worktree at `../<repo>-worktrees/<branch>/`
253
- 2. Creates or checks out the specified branch
254
- 3. Claude works in the worktree directory
255
- 4. Your main working directory stays untouched
256
-
257
- ### Environment Variable
258
-
259
- | Variable | Description |
260
- |----------|-------------|
261
- | `WORKTREE_MODE` | `prompt` (ask on new sessions), `require` (always require branch), `off` (disable) |
262
-
263
- ## Interactive Features
264
-
265
- ### Permission Approval
266
-
267
- When Claude wants to execute a tool (edit file, run command, etc.):
268
-
269
- - **πŸ‘ Allow** - Approve this specific action
270
- - **βœ… Allow all** - Approve all future actions this session
271
- - **πŸ‘Ž Deny** - Reject this action
272
-
273
- To skip prompts: `claude-threads --skip-permissions` or set `SKIP_PERMISSIONS=true`
274
-
275
- ### Plan Mode
276
-
277
- When Claude creates a plan and is ready to implement:
278
-
279
- - **πŸ‘** Approve and start building
280
- - **πŸ‘Ž** Request changes
281
-
282
- Once approved, subsequent plans auto-continue.
283
-
284
- ### Questions
285
-
286
- When Claude asks questions with multiple choice options:
287
-
288
- - React with 1️⃣ 2️⃣ 3️⃣ or 4️⃣ to answer
289
- - Questions are asked one at a time
290
-
291
- ### Task List
292
-
293
- Claude's todo list shows live in your chat and stays at the bottom of the thread:
294
-
295
- - β—‹ Pending
296
- - πŸ”„ In progress (shows elapsed time)
297
- - βœ… Completed
298
-
299
- React with πŸ”½ to collapse/expand the task list. Progress shown as `(2/5 Β· 40%)`.
300
-
301
- ### Channel Dashboard
302
-
303
- A pinned message at the bottom of the channel shows active sessions and recent history with system status, pending prompts, and current tasks. Session history is retained for up to 3 days.
304
-
305
- ### Session Header
306
-
307
- Each session shows a real-time status bar with context usage (color-coded πŸŸ’πŸŸ‘πŸŸ πŸ”΄), model name, cost, and uptime. The header table displays topic, directory, git branch, participants, and PR link (when working in a worktree with an associated pull request).
308
-
309
- ### Cancel Session
310
-
311
- Stop a running session:
312
-
313
- - Type `!stop` or `!cancel` in the thread
314
- - React with ❌ or πŸ›‘ to any message in the thread
315
-
316
- ### Session Persistence
317
-
318
- Sessions automatically survive bot restarts:
319
-
320
- - Active sessions are saved to `~/.config/claude-threads/sessions.json`
321
- - On restart, sessions resume with full context via Claude's `--resume` flag
322
- - Users see "Session resumed after bot restart" notification
323
- - Timed-out sessions can be resumed by reacting with πŸ”„ or sending a new message
324
-
325
- ### Image Attachments
326
-
327
- Attach images (JPEG, PNG, GIF, WebP) to your messages and Claude will analyze them. Works for both new sessions and follow-up messages.
328
-
329
- ### Thread Context
330
-
331
- When starting a session mid-thread (replying to existing conversation), you'll be prompted to include previous messages as context. Options include last 3, 5, 10, or all messages. Single-message threads auto-include context.
332
-
333
- ## Chrome Integration
334
-
335
- Enable browser automation with `--chrome` or `chrome: true` in config:
336
-
337
- ```bash
338
- claude-threads --chrome
339
- ```
340
-
341
- Claude can then control your Chrome browser for web tasks like:
342
- - Taking screenshots and analyzing pages
343
- - Filling forms and clicking buttons
344
- - Navigating and extracting content
345
-
346
- Requires the [Claude in Chrome](https://chromewebstore.google.com/detail/claude-in-chrome/) extension.
104
+ Or mid-session: `!worktree feature/add-auth`
347
105
 
348
106
  ## Access Control
349
107
 
350
- Set `allowedUsers` in your platform config to restrict who can use the bot:
108
+ Set `allowedUsers` in config to restrict who can use the bot:
351
109
 
352
110
  ```yaml
353
111
  platforms:
354
- - id: mattermost-main
355
- # ...
356
- allowedUsers: [alice, bob, carol]
357
-
358
112
  - id: slack-team
359
- # ...
360
- allowedUsers: [alice, bob] # Slack usernames (not user IDs)
361
- ```
362
-
363
- - Only listed users can start sessions
364
- - Only listed users can approve permissions
365
- - Session owners can `!invite` others temporarily
366
- - Empty = anyone can use (be careful!)
367
-
368
- ## Configuration
369
-
370
- Configuration is stored in YAML format at `~/.config/claude-threads/config.yaml`.
371
-
372
- ### Example Config
373
-
374
- ```yaml
375
- version: 1
376
- workingDir: /home/user/repos/myproject
377
- chrome: false
378
- worktreeMode: prompt
379
-
380
- platforms:
381
- # Mattermost example
382
- - id: mattermost-main
383
- type: mattermost
384
- displayName: Main Team
385
- url: https://chat.example.com
386
- token: your-bot-token
387
- channelId: abc123
388
- botName: claude-code
389
- allowedUsers: [alice, bob]
390
- skipPermissions: false
391
-
392
- # Slack example
393
- - id: slack-eng
394
- type: slack
395
- displayName: Engineering
396
- botToken: xoxb-your-bot-token # Bot User OAuth Token
397
- appToken: xapp-your-app-token # App-Level Token (Socket Mode)
398
- channelId: C0123456789
399
- botName: claude
400
- allowedUsers: [alice, bob] # Slack usernames
401
- skipPermissions: false
113
+ allowedUsers: [alice, bob, carol]
402
114
  ```
403
115
 
404
- ### Global Settings
116
+ Empty list = anyone can use (be careful!)
405
117
 
406
- | Setting | Description |
407
- |---------|-------------|
408
- | `workingDir` | Default working directory for Claude |
409
- | `chrome` | Enable Chrome integration (`true`/`false`) |
410
- | `worktreeMode` | Git worktree mode: `off`, `prompt`, or `require` |
118
+ ## Documentation
411
119
 
412
- ### Platform Settings (Mattermost)
120
+ - **[Mattermost Setup](docs/MATTERMOST_SETUP.md)** - Bot account setup for Mattermost
121
+ - **[Slack Setup](docs/SLACK_SETUP.md)** - Slack app setup with Socket Mode
122
+ - **[Configuration Reference](docs/CONFIGURATION.md)** - All settings and options
413
123
 
414
- | Setting | Description |
415
- |---------|-------------|
416
- | `url` | Mattermost server URL |
417
- | `token` | Bot access token |
418
- | `channelId` | Channel to listen in |
419
- | `botName` | Mention name (default: `claude-code`) |
420
- | `allowedUsers` | List of usernames who can use the bot |
421
- | `skipPermissions` | Auto-approve actions (`true`/`false`) |
422
-
423
- ### Platform Settings (Slack)
424
-
425
- | Setting | Description |
426
- |---------|-------------|
427
- | `botToken` | Bot User OAuth Token (`xoxb-...`) |
428
- | `appToken` | App-Level Token for Socket Mode (`xapp-...`) |
429
- | `channelId` | Channel ID to listen in (e.g., `C0123456789`) |
430
- | `botName` | Mention name (default: `claude`) |
431
- | `allowedUsers` | List of Slack usernames (e.g., `alice`, `bob`) |
432
- | `skipPermissions` | Auto-approve actions (`true`/`false`) |
433
-
434
- ### Environment Variables
435
-
436
- | Variable | Description |
437
- |----------|-------------|
438
- | `MAX_SESSIONS` | Max concurrent sessions (default: `5`) |
439
- | `SESSION_TIMEOUT_MS` | Idle timeout in ms (default: `1800000` = 30 min) |
440
- | `NO_UPDATE_NOTIFIER` | Set to `1` to disable update checks |
441
- | `DEBUG` | Set to `1` for verbose logging |
442
-
443
- ### Keep-Alive
444
-
445
- The bot automatically prevents system sleep while sessions are active (uses `caffeinate` on macOS, `systemd-inhibit` on Linux). Disable with `--no-keep-alive` or `keepAlive: false` in config.
446
-
447
- ## Code Display
448
-
449
- - **Edit**: Shows diff with `-` removed and `+` added lines
450
- - **Write**: Shows preview of new file content
451
- - **Bash**: Shows command being executed
452
- - **Read**: Shows file path being read
453
- - **MCP tools**: Shows tool name and server
454
-
455
- ## Auto-Updates
456
-
457
- claude-threads checks for updates every 30 minutes and notifies you when a new version is available:
458
-
459
- - **CLI**: Shows a notification box on startup
460
- - **Chat**: Shows a warning in session headers
461
-
462
- To update:
124
+ ## Updates
463
125
 
464
126
  ```bash
465
127
  bun install -g claude-threads
466
128
  ```
467
129
 
468
- To disable update checks, set `NO_UPDATE_NOTIFIER=1`.
469
-
470
- ## For Mattermost Admins
471
-
472
- To set up a bot account:
473
-
474
- 1. Go to **Integrations > Bot Accounts > Add Bot Account**
475
- 2. Give it a username (e.g., `claude-code`) and display name
476
- 3. Create a **Personal Access Token** for the bot
477
- 4. Add the bot to the channel where it should listen
478
-
479
- The bot needs permissions to:
480
- - Post messages
481
- - Add reactions
482
- - Read channel messages
483
-
484
- ## For Slack Admins
485
-
486
- Setting up claude-threads for Slack requires creating a Slack app with Socket Mode enabled.
487
-
488
- ### 1. Create Slack App
489
-
490
- 1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**
491
- 2. Choose **From scratch**
492
- 3. Name your app (e.g., "Claude Code") and select your workspace
493
-
494
- ### 2. Enable Socket Mode
495
-
496
- 1. Go to **Socket Mode** in the left sidebar
497
- 2. Toggle **Enable Socket Mode** to On
498
- 3. Create an **App-Level Token** with the `connections:write` scope
499
- 4. Save this token - it starts with `xapp-` (you'll need it for config)
500
-
501
- ### 3. Add Bot Scopes
502
-
503
- 1. Go to **OAuth & Permissions** in the left sidebar
504
- 2. Under **Bot Token Scopes**, add these scopes:
505
- - `channels:history` - Read messages in channels
506
- - `channels:read` - View basic channel info
507
- - `chat:write` - Send messages
508
- - `pins:read` - Read pinned messages
509
- - `pins:write` - Pin/unpin messages
510
- - `reactions:read` - Read emoji reactions
511
- - `reactions:write` - Add emoji reactions
512
- - `users:read` - View users and their info
513
-
514
- ### 4. Enable Events
515
-
516
- 1. Go to **Event Subscriptions** in the left sidebar
517
- 2. Toggle **Enable Events** to On
518
- 3. Under **Subscribe to bot events**, add:
519
- - `message.channels` - Messages in public channels
520
- - `reaction_added` - Reaction added to messages
521
- - `reaction_removed` - Reaction removed from messages
522
-
523
- ### 5. Install to Workspace
524
-
525
- 1. Go to **Install App** in the left sidebar
526
- 2. Click **Install to Workspace** and authorize
527
- 3. Copy the **Bot User OAuth Token** - it starts with `xoxb-`
528
-
529
- ### 6. Get Channel ID
530
-
531
- 1. In Slack, right-click the channel name and select **View channel details**
532
- 2. At the bottom, copy the **Channel ID** (starts with `C`)
533
-
534
- ### 7. Add Bot to Channel
535
-
536
- 1. In Slack, go to the channel where you want the bot
537
- 2. Type `/invite @YourBotName` or click the channel name β†’ **Integrations** β†’ **Add apps**
538
-
539
- ### Troubleshooting Slack
540
-
541
- **"not_authed" or "invalid_auth" errors:**
542
- - Verify your `botToken` starts with `xoxb-`
543
- - Verify your `appToken` starts with `xapp-`
544
- - Make sure the app is installed to your workspace
545
-
546
- **Bot not responding to messages:**
547
- - Check that Socket Mode is enabled
548
- - Verify `message.channels` event is subscribed
549
- - Make sure bot is invited to the channel
550
- - Check that username is in `allowedUsers`
551
-
552
- **Reactions not working:**
553
- - Verify `reactions:read` and `reactions:write` scopes are added
554
- - Check that `reaction_added` event is subscribed
130
+ The bot checks for updates automatically and notifies you when new versions are available.
555
131
 
556
132
  ## License
557
133
 
package/dist/index.js CHANGED
@@ -48770,7 +48770,9 @@ class MattermostClient extends EventEmitter {
48770
48770
  extension: mattermostFile.extension
48771
48771
  };
48772
48772
  }
48773
- async api(method, path, body) {
48773
+ MAX_RETRIES = 3;
48774
+ RETRY_DELAY_MS = 500;
48775
+ async api(method, path, body, retryCount = 0) {
48774
48776
  const url = `${this.url}/api/v4${path}`;
48775
48777
  log.debug(`API ${method} ${path}`);
48776
48778
  const response = await fetch(url, {
@@ -48783,6 +48785,12 @@ class MattermostClient extends EventEmitter {
48783
48785
  });
48784
48786
  if (!response.ok) {
48785
48787
  const text = await response.text();
48788
+ if (response.status === 500 && retryCount < this.MAX_RETRIES) {
48789
+ const delay = this.RETRY_DELAY_MS * Math.pow(2, retryCount);
48790
+ log.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
48791
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
48792
+ return this.api(method, path, body, retryCount + 1);
48793
+ }
48786
48794
  log.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
48787
48795
  throw new Error(`Mattermost API error ${response.status}: ${text}`);
48788
48796
  }
@@ -49336,6 +49344,7 @@ class SlackClient extends EventEmitter2 {
49336
49344
  reconnectDelay = 1000;
49337
49345
  isIntentionalDisconnect = false;
49338
49346
  isReconnecting = false;
49347
+ reconnectTimeout = null;
49339
49348
  userCache = new Map;
49340
49349
  usernameToIdCache = new Map;
49341
49350
  botUserId = null;
@@ -49346,6 +49355,8 @@ class SlackClient extends EventEmitter2 {
49346
49355
  HEARTBEAT_INTERVAL_MS = 30000;
49347
49356
  HEARTBEAT_TIMEOUT_MS = 60000;
49348
49357
  lastProcessedTs = null;
49358
+ processedMessages = new Set;
49359
+ MAX_PROCESSED_MESSAGES = 1000;
49349
49360
  rateLimitDelay = 0;
49350
49361
  rateLimitRetryAfter = 0;
49351
49362
  formatter = new SlackFormatter;
@@ -49528,17 +49539,27 @@ class SlackClient extends EventEmitter2 {
49528
49539
  wsLogger.warn(`WebSocket closed before hello event (code: ${event.code}, reason: ${event.reason || "none"})`);
49529
49540
  }
49530
49541
  doReject(new Error(`Socket Mode WebSocket closed before connection established (code: ${event.code})`));
49531
- if (!this.isIntentionalDisconnect) {
49542
+ const serverShutdown = event.reason?.toLowerCase().includes("server shutting down");
49543
+ const connectionReplaced = event.reason?.toLowerCase().includes("new connection replacing");
49544
+ if (!this.isIntentionalDisconnect && !serverShutdown && !connectionReplaced) {
49532
49545
  wsLogger.debug("Scheduling reconnect...");
49533
49546
  this.scheduleReconnect();
49534
49547
  } else {
49535
- wsLogger.debug("Intentional disconnect, not reconnecting");
49548
+ if (serverShutdown) {
49549
+ wsLogger.debug("Server shutdown detected, not reconnecting");
49550
+ } else if (connectionReplaced) {
49551
+ wsLogger.debug("Connection replaced by new one, not reconnecting");
49552
+ } else {
49553
+ wsLogger.debug("Intentional disconnect, not reconnecting");
49554
+ }
49536
49555
  }
49537
49556
  };
49538
49557
  this.ws.onerror = (event) => {
49539
49558
  clearTimeout(connectionTimeout);
49540
49559
  wsLogger.warn(`Socket Mode: WebSocket error: ${event}`);
49541
- this.emit("error", new Error("Socket Mode WebSocket error"));
49560
+ if (!this.isIntentionalDisconnect && !this.isReconnecting) {
49561
+ this.emit("error", new Error("Socket Mode WebSocket error"));
49562
+ }
49542
49563
  doReject(new Error("Socket Mode WebSocket error"));
49543
49564
  };
49544
49565
  });
@@ -49568,7 +49589,18 @@ class SlackClient extends EventEmitter2 {
49568
49589
  if (event.channel !== this.channelId) {
49569
49590
  return;
49570
49591
  }
49592
+ if (event.ts && this.processedMessages.has(event.ts)) {
49593
+ wsLogger.debug(`Ignoring duplicate message: ${event.ts}`);
49594
+ return;
49595
+ }
49571
49596
  if (event.ts) {
49597
+ this.processedMessages.add(event.ts);
49598
+ if (this.processedMessages.size > this.MAX_PROCESSED_MESSAGES) {
49599
+ const iterator = this.processedMessages.values();
49600
+ const first = iterator.next().value;
49601
+ if (first)
49602
+ this.processedMessages.delete(first);
49603
+ }
49572
49604
  this.lastProcessedTs = event.ts;
49573
49605
  }
49574
49606
  const message = {
@@ -49636,12 +49668,21 @@ class SlackClient extends EventEmitter2 {
49636
49668
  log2.error("Max reconnection attempts reached");
49637
49669
  return;
49638
49670
  }
49671
+ if (this.reconnectTimeout) {
49672
+ clearTimeout(this.reconnectTimeout);
49673
+ this.reconnectTimeout = null;
49674
+ }
49639
49675
  this.isReconnecting = true;
49640
49676
  this.reconnectAttempts++;
49641
49677
  const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
49642
49678
  log2.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
49643
49679
  this.emit("reconnecting", this.reconnectAttempts);
49644
- setTimeout(() => {
49680
+ this.reconnectTimeout = setTimeout(() => {
49681
+ this.reconnectTimeout = null;
49682
+ if (this.isIntentionalDisconnect) {
49683
+ wsLogger.debug("Skipping reconnect: intentional disconnect was called");
49684
+ return;
49685
+ }
49645
49686
  this.connect().catch((err) => {
49646
49687
  log2.error(`Reconnection failed: ${err}`);
49647
49688
  });
@@ -49703,6 +49744,10 @@ class SlackClient extends EventEmitter2 {
49703
49744
  wsLogger.info("Disconnecting Socket Mode WebSocket (intentional)");
49704
49745
  this.isIntentionalDisconnect = true;
49705
49746
  this.stopHeartbeat();
49747
+ if (this.reconnectTimeout) {
49748
+ clearTimeout(this.reconnectTimeout);
49749
+ this.reconnectTimeout = null;
49750
+ }
49706
49751
  if (this.ws) {
49707
49752
  this.ws.close();
49708
49753
  this.ws = null;
@@ -49809,10 +49854,13 @@ class SlackClient extends EventEmitter2 {
49809
49854
  }
49810
49855
  return `#${targetTs}`;
49811
49856
  }
49812
- async createPost(message, threadId) {
49857
+ async createPost(message, threadId, options) {
49858
+ const shouldUnfurl = options?.unfurl ?? threadId !== undefined;
49813
49859
  const body = {
49814
49860
  channel: this.channelId,
49815
- text: message
49861
+ text: message,
49862
+ unfurl_links: shouldUnfurl,
49863
+ unfurl_media: shouldUnfurl
49816
49864
  };
49817
49865
  if (threadId) {
49818
49866
  body.thread_ts = threadId;
@@ -49844,7 +49892,7 @@ class SlackClient extends EventEmitter2 {
49844
49892
  };
49845
49893
  }
49846
49894
  async createInteractivePost(message, reactions, threadId) {
49847
- const post = await this.createPost(message, threadId);
49895
+ const post = await this.createPost(message, threadId, { unfurl: false });
49848
49896
  for (const emoji of reactions) {
49849
49897
  try {
49850
49898
  await this.addReaction(post.id, emoji);
@@ -50769,38 +50817,43 @@ async function logAndNotify(error, context) {
50769
50817
 
50770
50818
  // src/session/post-helpers.ts
50771
50819
  var log6 = createLogger("post-helpers");
50820
+ async function createPostAndTrack(session, message) {
50821
+ const post = await session.platform.createPost(message, session.threadId);
50822
+ updateLastMessage(session, post);
50823
+ return post;
50824
+ }
50772
50825
  async function postInfo(session, message) {
50773
- return session.platform.createPost(message, session.threadId);
50826
+ return createPostAndTrack(session, message);
50774
50827
  }
50775
50828
  async function postSuccess(session, message) {
50776
- return session.platform.createPost(`\u2705 ${message}`, session.threadId);
50829
+ return createPostAndTrack(session, `\u2705 ${message}`);
50777
50830
  }
50778
50831
  async function postWarning(session, message) {
50779
- return session.platform.createPost(`\u26A0\uFE0F ${message}`, session.threadId);
50832
+ return createPostAndTrack(session, `\u26A0\uFE0F ${message}`);
50780
50833
  }
50781
50834
  async function postError(session, message) {
50782
- return session.platform.createPost(`\u274C ${message}`, session.threadId);
50835
+ return createPostAndTrack(session, `\u274C ${message}`);
50783
50836
  }
50784
50837
  async function postSecure(session, message) {
50785
- return session.platform.createPost(`\uD83D\uDD10 ${message}`, session.threadId);
50838
+ return createPostAndTrack(session, `\uD83D\uDD10 ${message}`);
50786
50839
  }
50787
50840
  async function postCommand(session, message) {
50788
- return session.platform.createPost(`\u2699\uFE0F ${message}`, session.threadId);
50841
+ return createPostAndTrack(session, `\u2699\uFE0F ${message}`);
50789
50842
  }
50790
50843
  async function postCancelled(session, message) {
50791
- return session.platform.createPost(`\uD83D\uDED1 ${message}`, session.threadId);
50844
+ return createPostAndTrack(session, `\uD83D\uDED1 ${message}`);
50792
50845
  }
50793
50846
  async function postResume(session, message) {
50794
- return session.platform.createPost(`\uD83D\uDD04 ${message}`, session.threadId);
50847
+ return createPostAndTrack(session, `\uD83D\uDD04 ${message}`);
50795
50848
  }
50796
50849
  async function postTimeout(session, message) {
50797
- return session.platform.createPost(`\u23F1\uFE0F ${message}`, session.threadId);
50850
+ return createPostAndTrack(session, `\u23F1\uFE0F ${message}`);
50798
50851
  }
50799
50852
  async function postInterrupt(session, message) {
50800
- return session.platform.createPost(`\u23F8\uFE0F ${message}`, session.threadId);
50853
+ return createPostAndTrack(session, `\u23F8\uFE0F ${message}`);
50801
50854
  }
50802
50855
  async function postUser(session, message) {
50803
- return session.platform.createPost(`\uD83D\uDC64 ${message}`, session.threadId);
50856
+ return createPostAndTrack(session, `\uD83D\uDC64 ${message}`);
50804
50857
  }
50805
50858
  function resetSessionActivity(session) {
50806
50859
  session.lastActivityAt = new Date;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
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",