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 +20 -0
- package/dist/claude/session.d.ts +1 -0
- package/dist/claude/session.js +40 -6
- package/dist/index.js +20 -18
- package/dist/logo.d.ts +5 -0
- package/dist/logo.js +11 -0
- package/package.json +1 -1
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
|
package/dist/claude/session.d.ts
CHANGED
package/dist/claude/session.js
CHANGED
|
@@ -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 {
|
|
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(`${
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
*/
|