mattermost-claude-code 0.10.0 → 0.10.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
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.2] - 2025-12-28
11
+
12
+ ### Changed
13
+ - Version number now displays directly after "mm-claude" in the logo instead of on a separate line
14
+
15
+ ### Fixed
16
+ - **Interrupt (⏸️) no longer kills session** - sessions now pause and can be resumed
17
+ - Previously SIGINT caused Claude CLI to exit and the session was lost
18
+ - Now session is preserved and user can send a new message to continue
19
+ - Works with both ⏸️ reaction and `!escape`/`!interrupt` commands
20
+ - **Filter `<thinking>` tags from output** - Claude's internal thinking is no longer shown to users
21
+ - Previously `<thinking>...</thinking>` tags would appear literally in Mattermost messages
22
+
23
+ ## [0.10.1] - 2025-12-28
24
+
25
+ ### Fixed
26
+ - **`!kill` now works from any message** - previously only worked within active session threads
27
+ - Can now send `!kill` or `@bot !kill` as the very first message to emergency shutdown
28
+ - Useful when bot is misbehaving and you need to stop it immediately
29
+
10
30
  ## [0.10.0] - 2025-12-28
11
31
 
12
32
  ### Added
@@ -58,6 +58,7 @@ interface Session {
58
58
  timeoutWarningPosted: boolean;
59
59
  isRestarting: boolean;
60
60
  isResumed: boolean;
61
+ wasInterrupted: boolean;
61
62
  }
62
63
  export declare class SessionManager {
63
64
  private mattermost;
@@ -2,7 +2,7 @@ import { ClaudeCli } from './cli.js';
2
2
  import { getUpdateInfo } from '../update-notifier.js';
3
3
  import { getReleaseNotes, getWhatsNewSummary } from '../changelog.js';
4
4
  import { SessionStore } from '../persistence/session-store.js';
5
- import { MATTERMOST_LOGO } from '../logo.js';
5
+ import { getMattermostLogo } from '../logo.js';
6
6
  import { randomUUID } from 'crypto';
7
7
  import { readFileSync } from 'fs';
8
8
  import { dirname, resolve } from 'path';
@@ -145,6 +145,7 @@ export class SessionManager {
145
145
  timeoutWarningPosted: false,
146
146
  isRestarting: false,
147
147
  isResumed: true,
148
+ wasInterrupted: false,
148
149
  };
149
150
  // Register session
150
151
  this.sessions.set(state.threadId, session);
@@ -275,7 +276,7 @@ export class SessionManager {
275
276
  // Post initial session message (will be updated by updateSessionHeader)
276
277
  let post;
277
278
  try {
278
- post = await this.mattermost.createPost(`${MATTERMOST_LOGO}\n**v${pkg.version}**\n\n*Starting session...*`, replyToPostId);
279
+ post = await this.mattermost.createPost(`${getMattermostLogo(pkg.version)}\n\n*Starting session...*`, replyToPostId);
279
280
  }
280
281
  catch (err) {
281
282
  console.error(` ❌ Failed to create session post:`, err);
@@ -320,6 +321,7 @@ export class SessionManager {
320
321
  timeoutWarningPosted: false,
321
322
  isRestarting: false,
322
323
  isResumed: false,
324
+ wasInterrupted: false,
323
325
  };
324
326
  // Register session
325
327
  this.sessions.set(actualThreadId, session);
@@ -743,7 +745,10 @@ export class SessionManager {
743
745
  const parts = [];
744
746
  for (const block of msg?.content || []) {
745
747
  if (block.type === 'text' && block.text) {
746
- parts.push(block.text);
748
+ // Filter out <thinking> tags that may appear in text content
749
+ const text = block.text.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
750
+ if (text)
751
+ parts.push(text);
747
752
  }
748
753
  else if (block.type === 'tool_use' && block.name) {
749
754
  const formatted = this.formatToolUse(block.name, block.input || {});
@@ -987,6 +992,34 @@ export class SessionManager {
987
992
  this.sessions.delete(threadId);
988
993
  return;
989
994
  }
995
+ // If session was interrupted (SIGINT sent), preserve for resume
996
+ // Claude CLI exits on SIGINT, but we want to allow resuming the session
997
+ if (session.wasInterrupted) {
998
+ console.log(` [exit] Session ${shortId}... exited after interrupt, preserving for resume`);
999
+ this.stopTyping(session);
1000
+ if (session.updateTimer) {
1001
+ clearTimeout(session.updateTimer);
1002
+ session.updateTimer = null;
1003
+ }
1004
+ // Update persistence with current state before cleanup
1005
+ this.persistSession(session);
1006
+ this.sessions.delete(threadId);
1007
+ // Clean up post index
1008
+ for (const [postId, tid] of this.postIndex.entries()) {
1009
+ if (tid === threadId) {
1010
+ this.postIndex.delete(postId);
1011
+ }
1012
+ }
1013
+ // Notify user they can send a new message to resume
1014
+ try {
1015
+ await this.mattermost.createPost(`ℹ️ Session paused. Send a new message to continue.`, session.threadId);
1016
+ }
1017
+ catch {
1018
+ // Ignore if we can't post
1019
+ }
1020
+ console.log(` ⏸️ Session paused (${shortId}…) — ${this.sessions.size} active`);
1021
+ return;
1022
+ }
990
1023
  // For resumed sessions that exit quickly (e.g., Claude --resume fails),
991
1024
  // don't unpersist immediately - give it a chance to be retried
992
1025
  if (session.isResumed && code !== 0) {
@@ -1102,10 +1135,12 @@ export class SessionManager {
1102
1135
  return;
1103
1136
  }
1104
1137
  const shortId = threadId.substring(0, 8);
1138
+ // Set flag BEFORE interrupt - if Claude exits due to SIGINT, we won't unpersist
1139
+ session.wasInterrupted = true;
1105
1140
  const interrupted = session.claude.interrupt();
1106
1141
  if (interrupted) {
1107
1142
  console.log(` ⏸️ Session (${shortId}…) interrupted by @${username}`);
1108
- await this.mattermost.createPost(`⏸️ **Interrupted** by @${username} — session still active, you can continue`, threadId);
1143
+ await this.mattermost.createPost(`⏸️ **Interrupted** by @${username}`, threadId);
1109
1144
  }
1110
1145
  }
1111
1146
  /** Change working directory for a session (restarts Claude CLI) */
@@ -1301,8 +1336,7 @@ export class SessionManager {
1301
1336
  const whatsNew = releaseNotes ? getWhatsNewSummary(releaseNotes) : '';
1302
1337
  const whatsNewLine = whatsNew ? `\n> ✨ **What's new:** ${whatsNew}\n` : '';
1303
1338
  const msg = [
1304
- MATTERMOST_LOGO,
1305
- `**v${pkg.version}**`,
1339
+ getMattermostLogo(pkg.version),
1306
1340
  updateNotice,
1307
1341
  whatsNewLine,
1308
1342
  `| | |`,
package/dist/index.js CHANGED
@@ -78,6 +78,25 @@ async function main() {
78
78
  const username = user?.username || 'unknown';
79
79
  const message = post.message;
80
80
  const threadRoot = post.root_id || post.id;
81
+ // Check for !kill command FIRST - works anywhere, even as the first message
82
+ const lowerMessage = message.trim().toLowerCase();
83
+ if (lowerMessage === '!kill' || (mattermost.isBotMentioned(message) && mattermost.extractPrompt(message).toLowerCase() === '!kill')) {
84
+ if (!mattermost.isUserAllowed(username)) {
85
+ await mattermost.createPost('⛔ Only authorized users can use `!kill`', threadRoot);
86
+ return;
87
+ }
88
+ // Notify all active sessions before killing
89
+ for (const tid of session.getActiveThreadIds()) {
90
+ try {
91
+ await mattermost.createPost(`🔴 **EMERGENCY SHUTDOWN** by @${username}`, tid);
92
+ }
93
+ catch { /* ignore */ }
94
+ }
95
+ console.log(` 🔴 EMERGENCY SHUTDOWN initiated by @${username}`);
96
+ session.killAllSessionsAndUnpersist();
97
+ mattermost.disconnect();
98
+ process.exit(1);
99
+ }
81
100
  // Follow-up in active thread
82
101
  if (session.isInSessionThread(threadRoot)) {
83
102
  // If message starts with @mention to someone else, ignore it (side conversation)
@@ -106,24 +125,7 @@ async function main() {
106
125
  }
107
126
  return;
108
127
  }
109
- // Check for !kill command (emergency shutdown - authorized users only)
110
- if (lowerContent === '!kill') {
111
- if (!mattermost.isUserAllowed(username)) {
112
- await mattermost.createPost('⛔ Only authorized users can use `!kill`', threadRoot);
113
- return;
114
- }
115
- // Notify all active sessions before killing
116
- for (const tid of session.getActiveThreadIds()) {
117
- try {
118
- await mattermost.createPost(`🔴 **EMERGENCY SHUTDOWN** by @${username}`, tid);
119
- }
120
- catch { /* ignore */ }
121
- }
122
- console.log(` 🔴 EMERGENCY SHUTDOWN initiated by @${username}`);
123
- session.killAllSessionsAndUnpersist();
124
- mattermost.disconnect();
125
- process.exit(1);
126
- }
128
+ // Note: !kill is handled at the top level, before session thread check
127
129
  // Check for !help command
128
130
  if (lowerContent === '!help' || lowerContent === 'help') {
129
131
  await mattermost.createPost(`**Available commands:**\n\n` +
package/dist/logo.d.ts CHANGED
@@ -10,8 +10,13 @@
10
10
  export declare const CLI_LOGO: string;
11
11
  /**
12
12
  * ASCII logo for Mattermost (plain text, no ANSI codes)
13
+ * Use getMattermostLogo(version) instead to include version
13
14
  */
14
15
  export declare const MATTERMOST_LOGO = "```\n \u2734 \u2590\u2588\u2599 \u259F\u2588\u258C \u2734 mm-claude\n\u2734 \u2590\u2588\u2590\u2588\u258C\u2588\u258C \u2734 Mattermost \u00D7 Claude Code\n \u2734 \u2590\u2588\u258C \u2590\u2588\u258C \u2734\n```";
16
+ /**
17
+ * Get ASCII logo for Mattermost with version included
18
+ */
19
+ export declare function getMattermostLogo(version: string): string;
15
20
  /**
16
21
  * Compact inline logo for Mattermost headers
17
22
  */
package/dist/logo.js CHANGED
@@ -24,12 +24,23 @@ ${colors.orange}✴${colors.reset} ${colors.blue}▐█▐█▌█▌${colors.
24
24
  `;
25
25
  /**
26
26
  * ASCII logo for Mattermost (plain text, no ANSI codes)
27
+ * Use getMattermostLogo(version) instead to include version
27
28
  */
28
29
  export const MATTERMOST_LOGO = `\`\`\`
29
30
  ✴ ▐█▙ ▟█▌ ✴ mm-claude
30
31
  ✴ ▐█▐█▌█▌ ✴ Mattermost × Claude Code
31
32
  ✴ ▐█▌ ▐█▌ ✴
32
33
  \`\`\``;
34
+ /**
35
+ * Get ASCII logo for Mattermost with version included
36
+ */
37
+ export function getMattermostLogo(version) {
38
+ return `\`\`\`
39
+ ✴ ▐█▙ ▟█▌ ✴ mm-claude v${version}
40
+ ✴ ▐█▐█▌█▌ ✴ Mattermost × Claude Code
41
+ ✴ ▐█▌ ▐█▌ ✴
42
+ \`\`\``;
43
+ }
33
44
  /**
34
45
  * Compact inline logo for Mattermost headers
35
46
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mattermost-claude-code",
3
- "version": "0.10.0",
3
+ "version": "0.10.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",