claude-threads 0.15.0 → 0.16.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +5 -5
  3. package/dist/index.js +20397 -387
  4. package/dist/mcp/permission-server.js +34039 -139
  5. package/package.json +14 -18
  6. package/dist/changelog.d.ts +0 -20
  7. package/dist/changelog.js +0 -134
  8. package/dist/claude/cli.d.ts +0 -50
  9. package/dist/claude/cli.js +0 -181
  10. package/dist/config/migration.d.ts +0 -45
  11. package/dist/config/migration.js +0 -35
  12. package/dist/config.d.ts +0 -21
  13. package/dist/config.js +0 -7
  14. package/dist/git/worktree.d.ts +0 -46
  15. package/dist/git/worktree.js +0 -228
  16. package/dist/index.d.ts +0 -2
  17. package/dist/logo.d.ts +0 -14
  18. package/dist/logo.js +0 -41
  19. package/dist/mattermost/api.d.ts +0 -85
  20. package/dist/mattermost/api.js +0 -124
  21. package/dist/mattermost/api.test.d.ts +0 -1
  22. package/dist/mattermost/api.test.js +0 -319
  23. package/dist/mcp/permission-server.d.ts +0 -2
  24. package/dist/onboarding.d.ts +0 -1
  25. package/dist/onboarding.js +0 -318
  26. package/dist/persistence/session-store.d.ts +0 -71
  27. package/dist/persistence/session-store.js +0 -152
  28. package/dist/platform/client.d.ts +0 -140
  29. package/dist/platform/client.js +0 -1
  30. package/dist/platform/formatter.d.ts +0 -74
  31. package/dist/platform/formatter.js +0 -1
  32. package/dist/platform/index.d.ts +0 -11
  33. package/dist/platform/index.js +0 -8
  34. package/dist/platform/mattermost/client.d.ts +0 -70
  35. package/dist/platform/mattermost/client.js +0 -404
  36. package/dist/platform/mattermost/formatter.d.ts +0 -20
  37. package/dist/platform/mattermost/formatter.js +0 -46
  38. package/dist/platform/mattermost/permission-api.d.ts +0 -10
  39. package/dist/platform/mattermost/permission-api.js +0 -139
  40. package/dist/platform/mattermost/types.d.ts +0 -71
  41. package/dist/platform/mattermost/types.js +0 -1
  42. package/dist/platform/permission-api-factory.d.ts +0 -11
  43. package/dist/platform/permission-api-factory.js +0 -21
  44. package/dist/platform/permission-api.d.ts +0 -67
  45. package/dist/platform/permission-api.js +0 -8
  46. package/dist/platform/types.d.ts +0 -70
  47. package/dist/platform/types.js +0 -7
  48. package/dist/session/commands.d.ts +0 -52
  49. package/dist/session/commands.js +0 -323
  50. package/dist/session/events.d.ts +0 -25
  51. package/dist/session/events.js +0 -368
  52. package/dist/session/index.d.ts +0 -7
  53. package/dist/session/index.js +0 -6
  54. package/dist/session/lifecycle.d.ts +0 -70
  55. package/dist/session/lifecycle.js +0 -456
  56. package/dist/session/manager.d.ts +0 -96
  57. package/dist/session/manager.js +0 -537
  58. package/dist/session/reactions.d.ts +0 -25
  59. package/dist/session/reactions.js +0 -151
  60. package/dist/session/streaming.d.ts +0 -47
  61. package/dist/session/streaming.js +0 -152
  62. package/dist/session/types.d.ts +0 -78
  63. package/dist/session/types.js +0 -9
  64. package/dist/session/worktree.d.ts +0 -56
  65. package/dist/session/worktree.js +0 -339
  66. package/dist/update-notifier.d.ts +0 -3
  67. package/dist/update-notifier.js +0 -41
  68. package/dist/utils/emoji.d.ts +0 -43
  69. package/dist/utils/emoji.js +0 -65
  70. package/dist/utils/emoji.test.d.ts +0 -1
  71. package/dist/utils/emoji.test.js +0 -131
  72. package/dist/utils/logger.d.ts +0 -34
  73. package/dist/utils/logger.js +0 -42
  74. package/dist/utils/logger.test.d.ts +0 -1
  75. package/dist/utils/logger.test.js +0 -121
  76. package/dist/utils/tool-formatter.d.ts +0 -53
  77. package/dist/utils/tool-formatter.js +0 -252
  78. package/dist/utils/tool-formatter.test.d.ts +0 -1
  79. package/dist/utils/tool-formatter.test.js +0 -372
@@ -1,537 +0,0 @@
1
- /**
2
- * SessionManager - Orchestrates Claude Code sessions across chat platforms
3
- *
4
- * This is the main coordinator that delegates to specialized modules:
5
- * - lifecycle.ts: Session start, resume, exit
6
- * - events.ts: Claude event handling
7
- * - reactions.ts: User reaction handling
8
- * - commands.ts: User commands (!cd, !invite, etc.)
9
- * - worktree.ts: Git worktree management
10
- * - streaming.ts: Message streaming and flushing
11
- */
12
- import { SessionStore } from '../persistence/session-store.js';
13
- import { isCancelEmoji, isEscapeEmoji, } from '../utils/emoji.js';
14
- // Import extracted modules
15
- import * as streaming from './streaming.js';
16
- import * as events from './events.js';
17
- import * as reactions from './reactions.js';
18
- import * as commands from './commands.js';
19
- import * as lifecycle from './lifecycle.js';
20
- import * as worktreeModule from './worktree.js';
21
- // Import constants for internal use
22
- import { MAX_SESSIONS, SESSION_TIMEOUT_MS, SESSION_WARNING_MS } from './types.js';
23
- /**
24
- * SessionManager - Main orchestrator for Claude Code sessions
25
- */
26
- export class SessionManager {
27
- // Platform management
28
- platforms = new Map();
29
- workingDir;
30
- skipPermissions;
31
- chromeEnabled;
32
- worktreeMode;
33
- debug = process.env.DEBUG === '1' || process.argv.includes('--debug');
34
- // Session state
35
- sessions = new Map();
36
- postIndex = new Map();
37
- // Persistence
38
- sessionStore = new SessionStore();
39
- // Cleanup
40
- cleanupTimer = null;
41
- // Shutdown flag
42
- isShuttingDown = false;
43
- constructor(workingDir, skipPermissions = false, chromeEnabled = false, worktreeMode = 'prompt') {
44
- this.workingDir = workingDir;
45
- this.skipPermissions = skipPermissions;
46
- this.chromeEnabled = chromeEnabled;
47
- this.worktreeMode = worktreeMode;
48
- // Start periodic cleanup
49
- this.cleanupTimer = setInterval(() => {
50
- lifecycle.cleanupIdleSessions(SESSION_TIMEOUT_MS, SESSION_WARNING_MS, this.getLifecycleContext());
51
- }, 60000);
52
- }
53
- // ---------------------------------------------------------------------------
54
- // Platform Management
55
- // ---------------------------------------------------------------------------
56
- addPlatform(platformId, client) {
57
- this.platforms.set(platformId, client);
58
- client.on('message', (post, user) => this.handleMessage(platformId, post, user));
59
- client.on('reaction', (reaction, user) => {
60
- if (user) {
61
- this.handleReaction(platformId, reaction.postId, reaction.emojiName, user.username);
62
- }
63
- });
64
- console.log(` 📡 Platform "${platformId}" registered`);
65
- }
66
- removePlatform(platformId) {
67
- this.platforms.delete(platformId);
68
- }
69
- // ---------------------------------------------------------------------------
70
- // Context Builders (for module delegation)
71
- // ---------------------------------------------------------------------------
72
- getLifecycleContext() {
73
- return {
74
- workingDir: this.workingDir,
75
- skipPermissions: this.skipPermissions,
76
- chromeEnabled: this.chromeEnabled,
77
- debug: this.debug,
78
- maxSessions: MAX_SESSIONS,
79
- sessions: this.sessions,
80
- postIndex: this.postIndex,
81
- platforms: this.platforms,
82
- sessionStore: this.sessionStore,
83
- isShuttingDown: this.isShuttingDown,
84
- getSessionId: (pid, tid) => this.getSessionId(pid, tid),
85
- findSessionByThreadId: (tid) => this.findSessionByThreadId(tid),
86
- handleEvent: (sid, e) => this.handleEvent(sid, e),
87
- handleExit: (sid, code) => this.handleExit(sid, code),
88
- registerPost: (pid, tid) => this.registerPost(pid, tid),
89
- startTyping: (s) => this.startTyping(s),
90
- stopTyping: (s) => this.stopTyping(s),
91
- flush: (s) => this.flush(s),
92
- persistSession: (s) => this.persistSession(s),
93
- unpersistSession: (sid) => this.unpersistSession(sid),
94
- updateSessionHeader: (s) => this.updateSessionHeader(s),
95
- shouldPromptForWorktree: (s) => this.shouldPromptForWorktree(s),
96
- postWorktreePrompt: (s, r) => this.postWorktreePrompt(s, r),
97
- buildMessageContent: (t, p, f) => this.buildMessageContent(t, p, f),
98
- };
99
- }
100
- getEventContext() {
101
- return {
102
- debug: this.debug,
103
- registerPost: (pid, tid) => this.registerPost(pid, tid),
104
- flush: (s) => this.flush(s),
105
- startTyping: (s) => this.startTyping(s),
106
- stopTyping: (s) => this.stopTyping(s),
107
- appendContent: (s, t) => this.appendContent(s, t),
108
- };
109
- }
110
- getReactionContext() {
111
- return {
112
- debug: this.debug,
113
- startTyping: (s) => this.startTyping(s),
114
- stopTyping: (s) => this.stopTyping(s),
115
- updateSessionHeader: (s) => this.updateSessionHeader(s),
116
- };
117
- }
118
- getCommandContext() {
119
- return {
120
- skipPermissions: this.skipPermissions,
121
- chromeEnabled: this.chromeEnabled,
122
- maxSessions: MAX_SESSIONS,
123
- handleEvent: (tid, e) => this.handleEvent(tid, e),
124
- handleExit: (tid, code) => this.handleExit(tid, code),
125
- flush: (s) => this.flush(s),
126
- startTyping: (s) => this.startTyping(s),
127
- stopTyping: (s) => this.stopTyping(s),
128
- persistSession: (s) => this.persistSession(s),
129
- killSession: (tid) => this.killSession(tid),
130
- registerPost: (pid, tid) => this.registerPost(pid, tid),
131
- };
132
- }
133
- // ---------------------------------------------------------------------------
134
- // Session ID and Post Index
135
- // ---------------------------------------------------------------------------
136
- getSessionId(platformId, threadId) {
137
- return `${platformId}:${threadId}`;
138
- }
139
- registerPost(postId, threadId) {
140
- this.postIndex.set(postId, threadId);
141
- }
142
- getSessionByPost(postId) {
143
- const threadId = this.postIndex.get(postId);
144
- if (!threadId)
145
- return undefined;
146
- return this.findSessionByThreadId(threadId);
147
- }
148
- // ---------------------------------------------------------------------------
149
- // Message Handling
150
- // ---------------------------------------------------------------------------
151
- async handleMessage(_platformId, _post, _user) {
152
- // Message handling is done by the platform client routing to startSession/sendFollowUp
153
- // This is just a placeholder for the event subscription
154
- }
155
- // ---------------------------------------------------------------------------
156
- // Reaction Handling
157
- // ---------------------------------------------------------------------------
158
- async handleReaction(platformId, postId, emojiName, username) {
159
- const session = this.getSessionByPost(postId);
160
- if (!session)
161
- return;
162
- // Verify this reaction is from the same platform
163
- if (session.platformId !== platformId)
164
- return;
165
- // Only process reactions from allowed users
166
- if (!session.sessionAllowedUsers.has(username) && !session.platform.isUserAllowed(username)) {
167
- return;
168
- }
169
- await this.handleSessionReaction(session, postId, emojiName, username);
170
- }
171
- async handleSessionReaction(session, postId, emojiName, username) {
172
- // Handle ❌ on worktree prompt
173
- if (session.worktreePromptPostId === postId && emojiName === 'x') {
174
- await worktreeModule.handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s) => this.startTyping(s));
175
- return;
176
- }
177
- // Handle cancel/escape reactions on session start post
178
- if (session.sessionStartPostId === postId) {
179
- if (isCancelEmoji(emojiName)) {
180
- await commands.cancelSession(session, username, this.getCommandContext());
181
- return;
182
- }
183
- if (isEscapeEmoji(emojiName)) {
184
- await commands.interruptSession(session, username);
185
- return;
186
- }
187
- }
188
- // Handle question reactions
189
- if (session.pendingQuestionSet?.currentPostId === postId) {
190
- await reactions.handleQuestionReaction(session, postId, emojiName, username, this.getReactionContext());
191
- return;
192
- }
193
- // Handle plan approval reactions
194
- if (session.pendingApproval?.postId === postId) {
195
- await reactions.handleApprovalReaction(session, emojiName, username, this.getReactionContext());
196
- return;
197
- }
198
- // Handle message approval reactions
199
- if (session.pendingMessageApproval?.postId === postId) {
200
- await reactions.handleMessageApprovalReaction(session, emojiName, username, this.getReactionContext());
201
- return;
202
- }
203
- }
204
- // ---------------------------------------------------------------------------
205
- // Event Handling (delegates to events module)
206
- // ---------------------------------------------------------------------------
207
- handleEvent(sessionId, event) {
208
- const session = this.sessions.get(sessionId);
209
- if (!session)
210
- return;
211
- events.handleEvent(session, event, this.getEventContext());
212
- }
213
- // ---------------------------------------------------------------------------
214
- // Exit Handling (delegates to lifecycle module)
215
- // ---------------------------------------------------------------------------
216
- async handleExit(sessionId, code) {
217
- await lifecycle.handleExit(sessionId, code, this.getLifecycleContext());
218
- }
219
- // ---------------------------------------------------------------------------
220
- // Streaming utilities (delegates to streaming module)
221
- // ---------------------------------------------------------------------------
222
- appendContent(session, text) {
223
- if (!text)
224
- return;
225
- session.pendingContent += text + '\n';
226
- streaming.scheduleUpdate(session, (s) => this.flush(s));
227
- }
228
- async flush(session) {
229
- await streaming.flush(session, (pid, tid) => this.registerPost(pid, tid));
230
- }
231
- startTyping(session) {
232
- streaming.startTyping(session);
233
- }
234
- stopTyping(session) {
235
- streaming.stopTyping(session);
236
- }
237
- async buildMessageContent(text, platform, files) {
238
- return streaming.buildMessageContent(text, platform, files, this.debug);
239
- }
240
- // ---------------------------------------------------------------------------
241
- // Worktree utilities
242
- // ---------------------------------------------------------------------------
243
- async shouldPromptForWorktree(session) {
244
- return worktreeModule.shouldPromptForWorktree(session, this.worktreeMode, (repoRoot, excludeId) => this.hasOtherSessionInRepo(repoRoot, excludeId));
245
- }
246
- hasOtherSessionInRepo(repoRoot, excludeThreadId) {
247
- for (const session of this.sessions.values()) {
248
- // Skip the session we're checking from (compare raw threadIds)
249
- if (session.threadId === excludeThreadId)
250
- continue;
251
- if (session.workingDir === repoRoot)
252
- return true;
253
- if (session.worktreeInfo?.repoRoot === repoRoot)
254
- return true;
255
- }
256
- return false;
257
- }
258
- async postWorktreePrompt(session, reason) {
259
- await worktreeModule.postWorktreePrompt(session, reason, (pid, tid) => this.registerPost(pid, tid));
260
- this.stopTyping(session);
261
- }
262
- // ---------------------------------------------------------------------------
263
- // Persistence
264
- // ---------------------------------------------------------------------------
265
- persistSession(session) {
266
- const state = {
267
- platformId: session.platformId,
268
- threadId: session.threadId,
269
- claudeSessionId: session.claudeSessionId,
270
- startedBy: session.startedBy,
271
- startedAt: session.startedAt.toISOString(),
272
- lastActivityAt: session.lastActivityAt.toISOString(),
273
- sessionNumber: session.sessionNumber,
274
- workingDir: session.workingDir,
275
- planApproved: session.planApproved,
276
- sessionAllowedUsers: [...session.sessionAllowedUsers],
277
- forceInteractivePermissions: session.forceInteractivePermissions,
278
- sessionStartPostId: session.sessionStartPostId,
279
- tasksPostId: session.tasksPostId,
280
- worktreeInfo: session.worktreeInfo,
281
- pendingWorktreePrompt: session.pendingWorktreePrompt,
282
- worktreePromptDisabled: session.worktreePromptDisabled,
283
- queuedPrompt: session.queuedPrompt,
284
- };
285
- this.sessionStore.save(session.sessionId, state);
286
- }
287
- unpersistSession(sessionId) {
288
- this.sessionStore.remove(sessionId);
289
- }
290
- // ---------------------------------------------------------------------------
291
- // Session Header
292
- // ---------------------------------------------------------------------------
293
- async updateSessionHeader(session) {
294
- await commands.updateSessionHeader(session, this.getCommandContext());
295
- }
296
- // ---------------------------------------------------------------------------
297
- // Public API
298
- // ---------------------------------------------------------------------------
299
- async initialize() {
300
- const persisted = this.sessionStore.load();
301
- if (persisted.size === 0)
302
- return;
303
- console.log(` 🔄 Found ${persisted.size} persisted session(s), attempting resume...`);
304
- for (const state of persisted.values()) {
305
- await lifecycle.resumeSession(state, this.getLifecycleContext());
306
- }
307
- }
308
- async startSession(options, username, replyToPostId, platformId = 'default') {
309
- await lifecycle.startSession(options, username, replyToPostId, platformId, this.getLifecycleContext());
310
- }
311
- // Helper to find session by threadId (sessions are keyed by composite platformId:threadId)
312
- findSessionByThreadId(threadId) {
313
- for (const session of this.sessions.values()) {
314
- if (session.threadId === threadId) {
315
- return session;
316
- }
317
- }
318
- return undefined;
319
- }
320
- // Helper to find persisted session by threadId (persisted sessions are keyed by composite sessionId)
321
- findPersistedByThreadId(threadId) {
322
- const persisted = this.sessionStore.load();
323
- for (const session of persisted.values()) {
324
- if (session.threadId === threadId) {
325
- return session;
326
- }
327
- }
328
- return undefined;
329
- }
330
- async sendFollowUp(threadId, message, files) {
331
- const session = this.findSessionByThreadId(threadId);
332
- if (!session || !session.claude.isRunning())
333
- return;
334
- await lifecycle.sendFollowUp(session, message, files, this.getLifecycleContext());
335
- }
336
- isSessionActive() {
337
- return this.sessions.size > 0;
338
- }
339
- isInSessionThread(threadRoot) {
340
- const session = this.findSessionByThreadId(threadRoot);
341
- return session !== undefined && session.claude.isRunning();
342
- }
343
- hasPausedSession(threadId) {
344
- if (this.findSessionByThreadId(threadId))
345
- return false;
346
- return this.findPersistedByThreadId(threadId) !== undefined;
347
- }
348
- async resumePausedSession(threadId, message, files) {
349
- await lifecycle.resumePausedSession(threadId, message, files, this.getLifecycleContext());
350
- }
351
- getPersistedSession(threadId) {
352
- return this.findPersistedByThreadId(threadId);
353
- }
354
- killSession(threadId, unpersist = true) {
355
- const session = this.findSessionByThreadId(threadId);
356
- if (!session)
357
- return;
358
- lifecycle.killSession(session, unpersist, this.getLifecycleContext());
359
- }
360
- killAllSessions() {
361
- lifecycle.killAllSessions(this.getLifecycleContext());
362
- }
363
- // Commands
364
- async cancelSession(threadId, username) {
365
- const session = this.findSessionByThreadId(threadId);
366
- if (!session)
367
- return;
368
- await commands.cancelSession(session, username, this.getCommandContext());
369
- }
370
- async interruptSession(threadId, username) {
371
- const session = this.findSessionByThreadId(threadId);
372
- if (!session)
373
- return;
374
- await commands.interruptSession(session, username);
375
- }
376
- async changeDirectory(threadId, newDir, username) {
377
- const session = this.findSessionByThreadId(threadId);
378
- if (!session)
379
- return;
380
- await commands.changeDirectory(session, newDir, username, this.getCommandContext());
381
- }
382
- async inviteUser(threadId, invitedUser, invitedBy) {
383
- const session = this.findSessionByThreadId(threadId);
384
- if (!session)
385
- return;
386
- await commands.inviteUser(session, invitedUser, invitedBy, this.getCommandContext());
387
- }
388
- async kickUser(threadId, kickedUser, kickedBy) {
389
- const session = this.findSessionByThreadId(threadId);
390
- if (!session)
391
- return;
392
- await commands.kickUser(session, kickedUser, kickedBy, this.getCommandContext());
393
- }
394
- async enableInteractivePermissions(threadId, username) {
395
- const session = this.findSessionByThreadId(threadId);
396
- if (!session)
397
- return;
398
- await commands.enableInteractivePermissions(session, username, this.getCommandContext());
399
- }
400
- isSessionInteractive(threadId) {
401
- const session = this.findSessionByThreadId(threadId);
402
- if (!session)
403
- return !this.skipPermissions;
404
- if (!this.skipPermissions)
405
- return true;
406
- return session.forceInteractivePermissions;
407
- }
408
- async requestMessageApproval(threadId, username, message) {
409
- const session = this.findSessionByThreadId(threadId);
410
- if (!session)
411
- return;
412
- await commands.requestMessageApproval(session, username, message, this.getCommandContext());
413
- }
414
- // Worktree commands
415
- async handleWorktreeBranchResponse(threadId, branchName, username) {
416
- const session = this.findSessionByThreadId(threadId);
417
- if (!session)
418
- return false;
419
- return worktreeModule.handleWorktreeBranchResponse(session, branchName, username, (tid, branch, user) => this.createAndSwitchToWorktree(tid, branch, user));
420
- }
421
- async handleWorktreeSkip(threadId, username) {
422
- const session = this.findSessionByThreadId(threadId);
423
- if (!session)
424
- return;
425
- await worktreeModule.handleWorktreeSkip(session, username, (s) => this.persistSession(s), (s) => this.startTyping(s));
426
- }
427
- async createAndSwitchToWorktree(threadId, branch, username) {
428
- const session = this.findSessionByThreadId(threadId);
429
- if (!session)
430
- return;
431
- await worktreeModule.createAndSwitchToWorktree(session, branch, username, {
432
- skipPermissions: this.skipPermissions,
433
- chromeEnabled: this.chromeEnabled,
434
- handleEvent: (tid, e) => this.handleEvent(tid, e),
435
- handleExit: (tid, code) => this.handleExit(tid, code),
436
- updateSessionHeader: (s) => this.updateSessionHeader(s),
437
- flush: (s) => this.flush(s),
438
- persistSession: (s) => this.persistSession(s),
439
- startTyping: (s) => this.startTyping(s),
440
- stopTyping: (s) => this.stopTyping(s),
441
- });
442
- }
443
- async switchToWorktree(threadId, branchOrPath, username) {
444
- const session = this.findSessionByThreadId(threadId);
445
- if (!session)
446
- return;
447
- await worktreeModule.switchToWorktree(session, branchOrPath, username, (tid, dir, user) => this.changeDirectory(tid, dir, user));
448
- }
449
- async listWorktreesCommand(threadId, _username) {
450
- const session = this.findSessionByThreadId(threadId);
451
- if (!session)
452
- return;
453
- await worktreeModule.listWorktreesCommand(session);
454
- }
455
- async removeWorktreeCommand(threadId, branchOrPath, username) {
456
- const session = this.findSessionByThreadId(threadId);
457
- if (!session)
458
- return;
459
- await worktreeModule.removeWorktreeCommand(session, branchOrPath, username);
460
- }
461
- async disableWorktreePrompt(threadId, username) {
462
- const session = this.findSessionByThreadId(threadId);
463
- if (!session)
464
- return;
465
- await worktreeModule.disableWorktreePrompt(session, username, (s) => this.persistSession(s));
466
- }
467
- hasPendingWorktreePrompt(threadId) {
468
- const session = this.findSessionByThreadId(threadId);
469
- return session?.pendingWorktreePrompt === true;
470
- }
471
- // Missing public methods needed by index.ts
472
- getActiveThreadIds() {
473
- // Return raw threadIds (not composite sessionIds) for posting to chat
474
- return [...this.sessions.values()].map(s => s.threadId);
475
- }
476
- killAllSessionsAndUnpersist() {
477
- for (const session of this.sessions.values()) {
478
- this.stopTyping(session);
479
- session.claude.kill();
480
- this.unpersistSession(session.sessionId);
481
- }
482
- this.sessions.clear();
483
- this.postIndex.clear();
484
- }
485
- isUserAllowedInSession(threadId, username) {
486
- const session = this.findSessionByThreadId(threadId);
487
- if (!session) {
488
- // Check persisted session
489
- const persisted = this.getPersistedSession(threadId);
490
- if (persisted) {
491
- return persisted.sessionAllowedUsers.includes(username) ||
492
- this.platforms.get(persisted.platformId)?.isUserAllowed(username) || false;
493
- }
494
- return false;
495
- }
496
- return session.sessionAllowedUsers.has(username) || session.platform.isUserAllowed(username);
497
- }
498
- async startSessionWithWorktree(options, branch, username, replyToPostId, platformId = 'default') {
499
- // Start normal session first
500
- await this.startSession(options, username, replyToPostId, platformId);
501
- // Then switch to worktree
502
- const threadId = replyToPostId || '';
503
- const session = this.sessions.get(this.getSessionId(platformId, threadId));
504
- if (session) {
505
- await this.createAndSwitchToWorktree(session.threadId, branch, username);
506
- }
507
- }
508
- setShuttingDown() {
509
- this.isShuttingDown = true;
510
- }
511
- // Shutdown
512
- async shutdown(message) {
513
- this.isShuttingDown = true;
514
- if (this.cleanupTimer) {
515
- clearInterval(this.cleanupTimer);
516
- this.cleanupTimer = null;
517
- }
518
- // Post shutdown message to all active sessions
519
- if (message) {
520
- for (const session of this.sessions.values()) {
521
- try {
522
- await session.platform.createPost(message, session.threadId);
523
- }
524
- catch {
525
- // Ignore
526
- }
527
- }
528
- }
529
- // Kill all sessions but preserve persistence
530
- for (const session of this.sessions.values()) {
531
- this.stopTyping(session);
532
- session.claude.kill();
533
- }
534
- this.sessions.clear();
535
- this.postIndex.clear();
536
- }
537
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * User reaction handling module
3
- *
4
- * Handles emoji reactions on posts: plan approval, question answers,
5
- * message approval, cancel/escape actions.
6
- */
7
- import type { Session } from './types.js';
8
- export interface ReactionContext {
9
- debug: boolean;
10
- startTyping: (session: Session) => void;
11
- stopTyping: (session: Session) => void;
12
- updateSessionHeader: (session: Session) => Promise<void>;
13
- }
14
- /**
15
- * Handle a reaction on a question post (number emoji to select an option).
16
- */
17
- export declare function handleQuestionReaction(session: Session, postId: string, emojiName: string, username: string, ctx: ReactionContext): Promise<void>;
18
- /**
19
- * Handle a reaction on a plan approval post (thumbs up/down).
20
- */
21
- export declare function handleApprovalReaction(session: Session, emojiName: string, username: string, ctx: ReactionContext): Promise<void>;
22
- /**
23
- * Handle a reaction on a message approval post (approve/invite/deny).
24
- */
25
- export declare function handleMessageApprovalReaction(session: Session, emoji: string, approver: string, ctx: ReactionContext): Promise<void>;