mattermost-claude-code 0.6.0 → 0.7.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/dist/claude/session.d.ts +3 -0
- package/dist/claude/session.js +91 -17
- package/dist/index.js +7 -0
- package/package.json +1 -1
package/dist/claude/session.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ interface Session {
|
|
|
37
37
|
startedAt: Date;
|
|
38
38
|
lastActivityAt: Date;
|
|
39
39
|
sessionNumber: number;
|
|
40
|
+
workingDir: string;
|
|
40
41
|
claude: ClaudeCli;
|
|
41
42
|
currentPostId: string | null;
|
|
42
43
|
pendingContent: string;
|
|
@@ -109,6 +110,8 @@ export declare class SessionManager {
|
|
|
109
110
|
killSession(threadId: string): void;
|
|
110
111
|
/** Cancel a session with user feedback */
|
|
111
112
|
cancelSession(threadId: string, username: string): Promise<void>;
|
|
113
|
+
/** Change working directory for a session (restarts Claude CLI) */
|
|
114
|
+
changeDirectory(threadId: string, newDir: string, username: string): Promise<void>;
|
|
112
115
|
/** Invite a user to participate in a specific session */
|
|
113
116
|
inviteUser(threadId: string, invitedUser: string, invitedBy: string): Promise<void>;
|
|
114
117
|
/** Kick a user from a specific session */
|
package/dist/claude/session.js
CHANGED
|
@@ -115,6 +115,7 @@ export class SessionManager {
|
|
|
115
115
|
startedAt: new Date(),
|
|
116
116
|
lastActivityAt: new Date(),
|
|
117
117
|
sessionNumber: this.sessions.size + 1,
|
|
118
|
+
workingDir: this.workingDir,
|
|
118
119
|
claude,
|
|
119
120
|
currentPostId: null,
|
|
120
121
|
pendingContent: '',
|
|
@@ -215,13 +216,14 @@ export class SessionManager {
|
|
|
215
216
|
session.activeSubagents.delete(toolUseId);
|
|
216
217
|
}
|
|
217
218
|
catch (err) {
|
|
218
|
-
console.error('
|
|
219
|
+
console.error(' ⚠️ Failed to update subagent completion:', err);
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
async handleExitPlanMode(session) {
|
|
222
223
|
// If already approved in this session, auto-continue
|
|
223
224
|
if (session.planApproved) {
|
|
224
|
-
|
|
225
|
+
if (this.debug)
|
|
226
|
+
console.log(' ↪ Plan already approved, auto-continuing');
|
|
225
227
|
if (session.claude.isRunning()) {
|
|
226
228
|
session.claude.sendMessage('Continue with the implementation.');
|
|
227
229
|
this.startTyping(session);
|
|
@@ -230,7 +232,8 @@ export class SessionManager {
|
|
|
230
232
|
}
|
|
231
233
|
// If we already have a pending approval, don't post another one
|
|
232
234
|
if (session.pendingApproval && session.pendingApproval.type === 'plan') {
|
|
233
|
-
|
|
235
|
+
if (this.debug)
|
|
236
|
+
console.log(' ↪ Plan approval already pending, waiting');
|
|
234
237
|
return;
|
|
235
238
|
}
|
|
236
239
|
// Flush any pending content first
|
|
@@ -251,7 +254,7 @@ export class SessionManager {
|
|
|
251
254
|
await this.mattermost.addReaction(post.id, '-1');
|
|
252
255
|
}
|
|
253
256
|
catch (err) {
|
|
254
|
-
console.error('
|
|
257
|
+
console.error(' ⚠️ Failed to add approval reactions:', err);
|
|
255
258
|
}
|
|
256
259
|
// Track this for reaction handling
|
|
257
260
|
session.pendingApproval = { postId: post.id, type: 'plan' };
|
|
@@ -267,7 +270,7 @@ export class SessionManager {
|
|
|
267
270
|
await this.mattermost.updatePost(session.tasksPostId, '📋 ~~Tasks~~ *(completed)*');
|
|
268
271
|
}
|
|
269
272
|
catch (err) {
|
|
270
|
-
console.error('
|
|
273
|
+
console.error(' ⚠️ Failed to update tasks:', err);
|
|
271
274
|
}
|
|
272
275
|
}
|
|
273
276
|
return;
|
|
@@ -303,7 +306,7 @@ export class SessionManager {
|
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
catch (err) {
|
|
306
|
-
console.error('
|
|
309
|
+
console.error(' ⚠️ Failed to update tasks:', err);
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
312
|
async handleTaskStart(session, toolUseId, input) {
|
|
@@ -318,13 +321,14 @@ export class SessionManager {
|
|
|
318
321
|
session.activeSubagents.set(toolUseId, post.id);
|
|
319
322
|
}
|
|
320
323
|
catch (err) {
|
|
321
|
-
console.error('
|
|
324
|
+
console.error(' ⚠️ Failed to post subagent status:', err);
|
|
322
325
|
}
|
|
323
326
|
}
|
|
324
327
|
async handleAskUserQuestion(session, toolUseId, input) {
|
|
325
328
|
// If we already have pending questions, don't start another set
|
|
326
329
|
if (session.pendingQuestionSet) {
|
|
327
|
-
|
|
330
|
+
if (this.debug)
|
|
331
|
+
console.log(' ↪ Questions already pending, waiting');
|
|
328
332
|
return;
|
|
329
333
|
}
|
|
330
334
|
// Flush any pending content first
|
|
@@ -381,7 +385,7 @@ export class SessionManager {
|
|
|
381
385
|
await this.mattermost.addReaction(post.id, REACTION_EMOJIS[i]);
|
|
382
386
|
}
|
|
383
387
|
catch (err) {
|
|
384
|
-
console.error(`
|
|
388
|
+
console.error(` ⚠️ Failed to add reaction ${REACTION_EMOJIS[i]}:`, err);
|
|
385
389
|
}
|
|
386
390
|
}
|
|
387
391
|
}
|
|
@@ -429,13 +433,14 @@ export class SessionManager {
|
|
|
429
433
|
return;
|
|
430
434
|
const selectedOption = question.options[optionIndex];
|
|
431
435
|
question.answer = selectedOption.label;
|
|
432
|
-
|
|
436
|
+
if (this.debug)
|
|
437
|
+
console.log(` 💬 @${username} answered "${question.header}": ${selectedOption.label}`);
|
|
433
438
|
// Update the post to show answer
|
|
434
439
|
try {
|
|
435
440
|
await this.mattermost.updatePost(postId, `✅ **${question.header}**: ${selectedOption.label}`);
|
|
436
441
|
}
|
|
437
442
|
catch (err) {
|
|
438
|
-
console.error('
|
|
443
|
+
console.error(' ⚠️ Failed to update answered question:', err);
|
|
439
444
|
}
|
|
440
445
|
// Move to next question or finish
|
|
441
446
|
session.pendingQuestionSet.currentIndex++;
|
|
@@ -449,7 +454,8 @@ export class SessionManager {
|
|
|
449
454
|
for (const q of questions) {
|
|
450
455
|
answersText += `- **${q.header}**: ${q.answer}\n`;
|
|
451
456
|
}
|
|
452
|
-
|
|
457
|
+
if (this.debug)
|
|
458
|
+
console.log(' ✅ All questions answered');
|
|
453
459
|
// Clear and send as regular message
|
|
454
460
|
session.pendingQuestionSet = null;
|
|
455
461
|
if (session.claude.isRunning()) {
|
|
@@ -466,7 +472,8 @@ export class SessionManager {
|
|
|
466
472
|
if (!isApprove && !isReject)
|
|
467
473
|
return;
|
|
468
474
|
const postId = session.pendingApproval.postId;
|
|
469
|
-
|
|
475
|
+
const shortId = session.threadId.substring(0, 8);
|
|
476
|
+
console.log(` ${isApprove ? '✅' : '❌'} Plan ${isApprove ? 'approved' : 'rejected'} (${shortId}…) by @${username}`);
|
|
470
477
|
// Update the post to show the decision
|
|
471
478
|
try {
|
|
472
479
|
const statusMessage = isApprove
|
|
@@ -475,7 +482,7 @@ export class SessionManager {
|
|
|
475
482
|
await this.mattermost.updatePost(postId, statusMessage);
|
|
476
483
|
}
|
|
477
484
|
catch (err) {
|
|
478
|
-
console.error('
|
|
485
|
+
console.error(' ⚠️ Failed to update approval post:', err);
|
|
479
486
|
}
|
|
480
487
|
// Clear pending approval and mark as approved
|
|
481
488
|
session.pendingApproval = null;
|
|
@@ -774,6 +781,72 @@ export class SessionManager {
|
|
|
774
781
|
await this.mattermost.createPost(`🛑 **Session cancelled** by @${username}`, threadId);
|
|
775
782
|
this.killSession(threadId);
|
|
776
783
|
}
|
|
784
|
+
/** Change working directory for a session (restarts Claude CLI) */
|
|
785
|
+
async changeDirectory(threadId, newDir, username) {
|
|
786
|
+
const session = this.sessions.get(threadId);
|
|
787
|
+
if (!session)
|
|
788
|
+
return;
|
|
789
|
+
// Only session owner or globally allowed users can change directory
|
|
790
|
+
if (session.startedBy !== username && !this.mattermost.isUserAllowed(username)) {
|
|
791
|
+
await this.mattermost.createPost(`⚠️ Only @${session.startedBy} or allowed users can change the working directory`, threadId);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// Expand ~ to home directory
|
|
795
|
+
const expandedDir = newDir.startsWith('~')
|
|
796
|
+
? newDir.replace('~', process.env.HOME || '')
|
|
797
|
+
: newDir;
|
|
798
|
+
// Resolve to absolute path
|
|
799
|
+
const { resolve } = await import('path');
|
|
800
|
+
const absoluteDir = resolve(expandedDir);
|
|
801
|
+
// Check if directory exists
|
|
802
|
+
const { existsSync, statSync } = await import('fs');
|
|
803
|
+
if (!existsSync(absoluteDir)) {
|
|
804
|
+
await this.mattermost.createPost(`❌ Directory does not exist: \`${newDir}\``, threadId);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (!statSync(absoluteDir).isDirectory()) {
|
|
808
|
+
await this.mattermost.createPost(`❌ Not a directory: \`${newDir}\``, threadId);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const shortId = threadId.substring(0, 8);
|
|
812
|
+
const shortDir = absoluteDir.replace(process.env.HOME || '', '~');
|
|
813
|
+
console.log(` 📂 Session (${shortId}…) changing directory to ${shortDir}`);
|
|
814
|
+
// Stop the current Claude CLI
|
|
815
|
+
this.stopTyping(session);
|
|
816
|
+
session.claude.kill();
|
|
817
|
+
// Flush any pending content
|
|
818
|
+
await this.flush(session);
|
|
819
|
+
session.currentPostId = null;
|
|
820
|
+
session.pendingContent = '';
|
|
821
|
+
// Update session working directory
|
|
822
|
+
session.workingDir = absoluteDir;
|
|
823
|
+
// Create new Claude CLI with new working directory
|
|
824
|
+
const cliOptions = {
|
|
825
|
+
workingDir: absoluteDir,
|
|
826
|
+
threadId: threadId,
|
|
827
|
+
skipPermissions: this.skipPermissions || !session.forceInteractivePermissions,
|
|
828
|
+
};
|
|
829
|
+
session.claude = new ClaudeCli(cliOptions);
|
|
830
|
+
// Rebind event handlers
|
|
831
|
+
session.claude.on('event', (e) => this.handleEvent(threadId, e));
|
|
832
|
+
session.claude.on('exit', (code) => this.handleExit(threadId, code));
|
|
833
|
+
// Start the new Claude CLI
|
|
834
|
+
try {
|
|
835
|
+
session.claude.start();
|
|
836
|
+
}
|
|
837
|
+
catch (err) {
|
|
838
|
+
console.error(' ❌ Failed to restart Claude:', err);
|
|
839
|
+
await this.mattermost.createPost(`❌ Failed to restart Claude: ${err}`, threadId);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
// Update session header with new directory
|
|
843
|
+
await this.updateSessionHeader(session);
|
|
844
|
+
// Post confirmation
|
|
845
|
+
await this.mattermost.createPost(`📂 **Working directory changed** to \`${shortDir}\`\n*Claude Code restarted in new directory*`, threadId);
|
|
846
|
+
// Update activity
|
|
847
|
+
session.lastActivityAt = new Date();
|
|
848
|
+
session.timeoutWarningPosted = false;
|
|
849
|
+
}
|
|
777
850
|
/** Invite a user to participate in a specific session */
|
|
778
851
|
async inviteUser(threadId, invitedUser, invitedBy) {
|
|
779
852
|
const session = this.sessions.get(threadId);
|
|
@@ -861,7 +934,8 @@ export class SessionManager {
|
|
|
861
934
|
async updateSessionHeader(session) {
|
|
862
935
|
if (!session.sessionStartPostId)
|
|
863
936
|
return;
|
|
864
|
-
|
|
937
|
+
// Use session's working directory (can be changed via !cd)
|
|
938
|
+
const shortDir = session.workingDir.replace(process.env.HOME || '', '~');
|
|
865
939
|
// Check session-level permission override
|
|
866
940
|
const isInteractive = !this.skipPermissions || session.forceInteractivePermissions;
|
|
867
941
|
const permMode = isInteractive ? '🔐 Interactive' : '⚡ Auto';
|
|
@@ -895,7 +969,7 @@ export class SessionManager {
|
|
|
895
969
|
await this.mattermost.updatePost(session.sessionStartPostId, msg);
|
|
896
970
|
}
|
|
897
971
|
catch (err) {
|
|
898
|
-
console.error('
|
|
972
|
+
console.error(' ⚠️ Failed to update session header:', err);
|
|
899
973
|
}
|
|
900
974
|
}
|
|
901
975
|
/** Request approval for a message from an unauthorized user */
|
|
@@ -923,7 +997,7 @@ export class SessionManager {
|
|
|
923
997
|
await this.mattermost.addReaction(post.id, '-1');
|
|
924
998
|
}
|
|
925
999
|
catch (err) {
|
|
926
|
-
console.error('
|
|
1000
|
+
console.error(' ⚠️ Failed to add message approval reactions:', err);
|
|
927
1001
|
}
|
|
928
1002
|
}
|
|
929
1003
|
/** Kill all active sessions (for graceful shutdown) */
|
package/dist/index.js
CHANGED
|
@@ -101,6 +101,7 @@ async function main() {
|
|
|
101
101
|
`| Command | Description |\n` +
|
|
102
102
|
`|:--------|:------------|\n` +
|
|
103
103
|
`| \`!help\` | Show this help message |\n` +
|
|
104
|
+
`| \`!cd <path>\` | Change working directory (restarts Claude) |\n` +
|
|
104
105
|
`| \`!invite @user\` | Invite a user to this session |\n` +
|
|
105
106
|
`| \`!kick @user\` | Remove an invited user |\n` +
|
|
106
107
|
`| \`!permissions interactive\` | Enable interactive permissions |\n` +
|
|
@@ -135,6 +136,12 @@ async function main() {
|
|
|
135
136
|
}
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
139
|
+
// Check for !cd command
|
|
140
|
+
const cdMatch = content.match(/^!cd\s+(.+)/i);
|
|
141
|
+
if (cdMatch) {
|
|
142
|
+
await session.changeDirectory(threadRoot, cdMatch[1].trim(), username);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
138
145
|
// Check if user is allowed in this session
|
|
139
146
|
if (!session.isUserAllowedInSession(threadRoot, username)) {
|
|
140
147
|
// Request approval for their message
|