claude-threads 0.13.0 → 0.14.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +78 -28
  3. package/dist/claude/cli.d.ts +8 -0
  4. package/dist/claude/cli.js +16 -8
  5. package/dist/config/migration.d.ts +45 -0
  6. package/dist/config/migration.js +35 -0
  7. package/dist/config.d.ts +12 -18
  8. package/dist/config.js +7 -94
  9. package/dist/git/worktree.d.ts +0 -4
  10. package/dist/git/worktree.js +1 -1
  11. package/dist/index.js +31 -13
  12. package/dist/logo.d.ts +3 -20
  13. package/dist/logo.js +7 -23
  14. package/dist/mcp/permission-server.js +61 -112
  15. package/dist/onboarding.js +262 -137
  16. package/dist/persistence/session-store.d.ts +8 -2
  17. package/dist/persistence/session-store.js +41 -16
  18. package/dist/platform/client.d.ts +140 -0
  19. package/dist/platform/formatter.d.ts +74 -0
  20. package/dist/platform/index.d.ts +11 -0
  21. package/dist/platform/index.js +8 -0
  22. package/dist/platform/mattermost/client.d.ts +70 -0
  23. package/dist/{mattermost → platform/mattermost}/client.js +117 -34
  24. package/dist/platform/mattermost/formatter.d.ts +20 -0
  25. package/dist/platform/mattermost/formatter.js +46 -0
  26. package/dist/platform/mattermost/permission-api.d.ts +10 -0
  27. package/dist/platform/mattermost/permission-api.js +139 -0
  28. package/dist/platform/mattermost/types.js +1 -0
  29. package/dist/platform/permission-api-factory.d.ts +11 -0
  30. package/dist/platform/permission-api-factory.js +21 -0
  31. package/dist/platform/permission-api.d.ts +67 -0
  32. package/dist/platform/permission-api.js +8 -0
  33. package/dist/platform/types.d.ts +70 -0
  34. package/dist/platform/types.js +7 -0
  35. package/dist/session/commands.d.ts +52 -0
  36. package/dist/session/commands.js +323 -0
  37. package/dist/session/events.d.ts +25 -0
  38. package/dist/session/events.js +368 -0
  39. package/dist/session/index.d.ts +7 -0
  40. package/dist/session/index.js +6 -0
  41. package/dist/session/lifecycle.d.ts +70 -0
  42. package/dist/session/lifecycle.js +456 -0
  43. package/dist/session/manager.d.ts +96 -0
  44. package/dist/session/manager.js +537 -0
  45. package/dist/session/reactions.d.ts +25 -0
  46. package/dist/session/reactions.js +151 -0
  47. package/dist/session/streaming.d.ts +47 -0
  48. package/dist/session/streaming.js +152 -0
  49. package/dist/session/types.d.ts +78 -0
  50. package/dist/session/types.js +9 -0
  51. package/dist/session/worktree.d.ts +56 -0
  52. package/dist/session/worktree.js +339 -0
  53. package/dist/{mattermost → utils}/emoji.d.ts +3 -3
  54. package/dist/{mattermost → utils}/emoji.js +3 -3
  55. package/dist/utils/emoji.test.d.ts +1 -0
  56. package/dist/utils/tool-formatter.d.ts +10 -13
  57. package/dist/utils/tool-formatter.js +48 -43
  58. package/dist/utils/tool-formatter.test.js +67 -52
  59. package/package.json +2 -3
  60. package/dist/claude/session.d.ts +0 -256
  61. package/dist/claude/session.js +0 -1964
  62. package/dist/mattermost/client.d.ts +0 -56
  63. /package/dist/{mattermost/emoji.test.d.ts → platform/client.js} +0 -0
  64. /package/dist/{mattermost/types.js → platform/formatter.js} +0 -0
  65. /package/dist/{mattermost → platform/mattermost}/types.d.ts +0 -0
  66. /package/dist/{mattermost → utils}/emoji.test.js +0 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Session lifecycle management module
3
+ *
4
+ * Handles session start, resume, exit, cleanup, and shutdown.
5
+ */
6
+ import { ClaudeCli } from '../claude/cli.js';
7
+ import { getLogo } from '../logo.js';
8
+ import { randomUUID } from 'crypto';
9
+ import { readFileSync } from 'fs';
10
+ import { dirname, resolve } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', '..', 'package.json'), 'utf-8'));
14
+ /**
15
+ * Helper to find a persisted session by raw threadId.
16
+ * Persisted sessions are keyed by composite sessionId, so we need to iterate.
17
+ */
18
+ function findPersistedByThreadId(persisted, threadId) {
19
+ for (const session of persisted.values()) {
20
+ if (session.threadId === threadId) {
21
+ return session;
22
+ }
23
+ }
24
+ return undefined;
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // Session creation
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Create a new session for a thread.
31
+ */
32
+ export async function startSession(options, username, replyToPostId, platformId, ctx) {
33
+ const threadId = replyToPostId || '';
34
+ // Check if session already exists for this thread
35
+ const existingSessionId = ctx.getSessionId(platformId, threadId);
36
+ const existingSession = ctx.sessions.get(existingSessionId);
37
+ if (existingSession && existingSession.claude.isRunning()) {
38
+ // Send as follow-up instead
39
+ await sendFollowUp(existingSession, options.prompt, options.files, ctx);
40
+ return;
41
+ }
42
+ const platform = ctx.platforms.get(platformId);
43
+ if (!platform) {
44
+ throw new Error(`Platform '${platformId}' not found. Call addPlatform() first.`);
45
+ }
46
+ // Check max sessions limit
47
+ if (ctx.sessions.size >= ctx.maxSessions) {
48
+ await platform.createPost(`⚠️ **Too busy** - ${ctx.sessions.size} sessions active. Please try again later.`, replyToPostId);
49
+ return;
50
+ }
51
+ // Post initial session message
52
+ let post;
53
+ try {
54
+ post = await platform.createPost(`${getLogo(pkg.version)}\n\n*Starting session...*`, replyToPostId);
55
+ }
56
+ catch (err) {
57
+ console.error(` ❌ Failed to create session post:`, err);
58
+ return;
59
+ }
60
+ const actualThreadId = replyToPostId || post.id;
61
+ const sessionId = ctx.getSessionId(platformId, actualThreadId);
62
+ // Generate a unique session ID for this Claude session
63
+ const claudeSessionId = randomUUID();
64
+ // Create Claude CLI with options
65
+ const platformMcpConfig = platform.getMcpConfig();
66
+ const cliOptions = {
67
+ workingDir: ctx.workingDir,
68
+ threadId: actualThreadId,
69
+ skipPermissions: ctx.skipPermissions,
70
+ sessionId: claudeSessionId,
71
+ resume: false,
72
+ chrome: ctx.chromeEnabled,
73
+ platformConfig: platformMcpConfig,
74
+ };
75
+ const claude = new ClaudeCli(cliOptions);
76
+ // Create the session object
77
+ const session = {
78
+ platformId,
79
+ threadId: actualThreadId,
80
+ sessionId,
81
+ platform,
82
+ claudeSessionId,
83
+ startedBy: username,
84
+ startedAt: new Date(),
85
+ lastActivityAt: new Date(),
86
+ sessionNumber: ctx.sessions.size + 1,
87
+ workingDir: ctx.workingDir,
88
+ claude,
89
+ currentPostId: null,
90
+ pendingContent: '',
91
+ pendingApproval: null,
92
+ pendingQuestionSet: null,
93
+ pendingMessageApproval: null,
94
+ planApproved: false,
95
+ sessionAllowedUsers: new Set([username]),
96
+ forceInteractivePermissions: false,
97
+ sessionStartPostId: post.id,
98
+ tasksPostId: null,
99
+ activeSubagents: new Map(),
100
+ updateTimer: null,
101
+ typingTimer: null,
102
+ timeoutWarningPosted: false,
103
+ isRestarting: false,
104
+ isResumed: false,
105
+ wasInterrupted: false,
106
+ inProgressTaskStart: null,
107
+ activeToolStarts: new Map(),
108
+ };
109
+ // Register session
110
+ ctx.sessions.set(sessionId, session);
111
+ ctx.registerPost(post.id, actualThreadId);
112
+ const shortId = actualThreadId.substring(0, 8);
113
+ console.log(` ▶ Session #${ctx.sessions.size} started (${shortId}…) by @${username}`);
114
+ // Update the header with full session info
115
+ await ctx.updateSessionHeader(session);
116
+ // Start typing indicator
117
+ ctx.startTyping(session);
118
+ // Bind event handlers (use sessionId which is the composite key)
119
+ claude.on('event', (e) => ctx.handleEvent(sessionId, e));
120
+ claude.on('exit', (code) => ctx.handleExit(sessionId, code));
121
+ try {
122
+ claude.start();
123
+ }
124
+ catch (err) {
125
+ console.error(' ❌ Failed to start Claude:', err);
126
+ ctx.stopTyping(session);
127
+ await session.platform.createPost(`❌ ${err}`, actualThreadId);
128
+ ctx.sessions.delete(session.sessionId);
129
+ return;
130
+ }
131
+ // Check if we should prompt for worktree
132
+ const shouldPrompt = await ctx.shouldPromptForWorktree(session);
133
+ if (shouldPrompt) {
134
+ session.queuedPrompt = options.prompt;
135
+ session.pendingWorktreePrompt = true;
136
+ await ctx.postWorktreePrompt(session, shouldPrompt);
137
+ ctx.persistSession(session);
138
+ return;
139
+ }
140
+ // Send the message to Claude
141
+ const content = await ctx.buildMessageContent(options.prompt, session.platform, options.files);
142
+ claude.sendMessage(content);
143
+ // Persist session for resume after restart
144
+ ctx.persistSession(session);
145
+ }
146
+ /**
147
+ * Resume a session from persisted state.
148
+ */
149
+ export async function resumeSession(state, ctx) {
150
+ const shortId = state.threadId.substring(0, 8);
151
+ // Get platform for this session
152
+ const platform = ctx.platforms.get(state.platformId);
153
+ if (!platform) {
154
+ console.log(` ⚠️ Platform ${state.platformId} not registered, skipping resume for ${shortId}...`);
155
+ return;
156
+ }
157
+ // Verify thread still exists
158
+ const post = await platform.getPost(state.threadId);
159
+ if (!post) {
160
+ console.log(` ⚠️ Thread ${shortId}... deleted, skipping resume`);
161
+ ctx.sessionStore.remove(`${state.platformId}:${state.threadId}`);
162
+ return;
163
+ }
164
+ // Check max sessions limit
165
+ if (ctx.sessions.size >= ctx.maxSessions) {
166
+ console.log(` ⚠️ Max sessions reached, skipping resume for ${shortId}...`);
167
+ return;
168
+ }
169
+ const platformId = state.platformId;
170
+ const sessionId = ctx.getSessionId(platformId, state.threadId);
171
+ // Create Claude CLI with resume flag
172
+ const skipPerms = ctx.skipPermissions && !state.forceInteractivePermissions;
173
+ const platformMcpConfig = platform.getMcpConfig();
174
+ const cliOptions = {
175
+ workingDir: state.workingDir,
176
+ threadId: state.threadId,
177
+ skipPermissions: skipPerms,
178
+ sessionId: state.claudeSessionId,
179
+ resume: true,
180
+ chrome: ctx.chromeEnabled,
181
+ platformConfig: platformMcpConfig,
182
+ };
183
+ const claude = new ClaudeCli(cliOptions);
184
+ // Rebuild Session object from persisted state
185
+ const session = {
186
+ platformId,
187
+ threadId: state.threadId,
188
+ sessionId,
189
+ platform,
190
+ claudeSessionId: state.claudeSessionId,
191
+ startedBy: state.startedBy,
192
+ startedAt: new Date(state.startedAt),
193
+ lastActivityAt: new Date(),
194
+ sessionNumber: state.sessionNumber,
195
+ workingDir: state.workingDir,
196
+ claude,
197
+ currentPostId: null,
198
+ pendingContent: '',
199
+ pendingApproval: null,
200
+ pendingQuestionSet: null,
201
+ pendingMessageApproval: null,
202
+ planApproved: state.planApproved,
203
+ sessionAllowedUsers: new Set(state.sessionAllowedUsers),
204
+ forceInteractivePermissions: state.forceInteractivePermissions,
205
+ sessionStartPostId: state.sessionStartPostId,
206
+ tasksPostId: state.tasksPostId,
207
+ activeSubagents: new Map(),
208
+ updateTimer: null,
209
+ typingTimer: null,
210
+ timeoutWarningPosted: false,
211
+ isRestarting: false,
212
+ isResumed: true,
213
+ wasInterrupted: false,
214
+ inProgressTaskStart: null,
215
+ activeToolStarts: new Map(),
216
+ worktreeInfo: state.worktreeInfo,
217
+ pendingWorktreePrompt: state.pendingWorktreePrompt,
218
+ worktreePromptDisabled: state.worktreePromptDisabled,
219
+ queuedPrompt: state.queuedPrompt,
220
+ };
221
+ // Register session
222
+ ctx.sessions.set(sessionId, session);
223
+ if (state.sessionStartPostId) {
224
+ ctx.registerPost(state.sessionStartPostId, state.threadId);
225
+ }
226
+ // Bind event handlers (use sessionId which is the composite key)
227
+ claude.on('event', (e) => ctx.handleEvent(sessionId, e));
228
+ claude.on('exit', (code) => ctx.handleExit(sessionId, code));
229
+ try {
230
+ claude.start();
231
+ console.log(` 🔄 Resumed session ${shortId}... (@${state.startedBy})`);
232
+ // Post resume message
233
+ await session.platform.createPost(`🔄 **Session resumed** after bot restart (v${pkg.version})\n*Reconnected to Claude session. You can continue where you left off.*`, state.threadId);
234
+ // Update session header
235
+ await ctx.updateSessionHeader(session);
236
+ // Update persistence with new activity time
237
+ ctx.persistSession(session);
238
+ }
239
+ catch (err) {
240
+ console.error(` ❌ Failed to resume session ${shortId}...:`, err);
241
+ ctx.sessions.delete(sessionId);
242
+ ctx.sessionStore.remove(sessionId);
243
+ // Try to notify user
244
+ try {
245
+ await session.platform.createPost(`⚠️ **Could not resume previous session.** Starting fresh.\n*Your previous conversation context is preserved, but Claude needs to re-read it.*`, state.threadId);
246
+ }
247
+ catch {
248
+ // Ignore if we can't post
249
+ }
250
+ }
251
+ }
252
+ // ---------------------------------------------------------------------------
253
+ // Session messaging
254
+ // ---------------------------------------------------------------------------
255
+ /**
256
+ * Send a follow-up message to an existing session.
257
+ */
258
+ export async function sendFollowUp(session, message, files, ctx) {
259
+ if (!session.claude.isRunning())
260
+ return;
261
+ const content = await ctx.buildMessageContent(message, session.platform, files);
262
+ session.claude.sendMessage(content);
263
+ session.lastActivityAt = new Date();
264
+ ctx.startTyping(session);
265
+ }
266
+ /**
267
+ * Resume a paused session and send a message to it.
268
+ */
269
+ export async function resumePausedSession(threadId, message, files, ctx) {
270
+ // Find persisted session by raw threadId
271
+ const persisted = ctx.sessionStore.load();
272
+ const state = findPersistedByThreadId(persisted, threadId);
273
+ if (!state) {
274
+ console.log(` [resume] No persisted session found for ${threadId.substring(0, 8)}...`);
275
+ return;
276
+ }
277
+ const shortId = threadId.substring(0, 8);
278
+ console.log(` 🔄 Resuming paused session ${shortId}... for new message`);
279
+ // Resume the session
280
+ await resumeSession(state, ctx);
281
+ // Wait a moment for the session to be ready, then send the message
282
+ const session = ctx.findSessionByThreadId(threadId);
283
+ if (session && session.claude.isRunning()) {
284
+ const content = await ctx.buildMessageContent(message, session.platform, files);
285
+ session.claude.sendMessage(content);
286
+ session.lastActivityAt = new Date();
287
+ ctx.startTyping(session);
288
+ }
289
+ else {
290
+ console.log(` ⚠️ Failed to resume session ${shortId}..., could not send message`);
291
+ }
292
+ }
293
+ // ---------------------------------------------------------------------------
294
+ // Session termination
295
+ // ---------------------------------------------------------------------------
296
+ /**
297
+ * Handle Claude CLI exit event.
298
+ */
299
+ export async function handleExit(sessionId, code, ctx) {
300
+ const session = ctx.sessions.get(sessionId);
301
+ const shortId = sessionId.substring(0, 8);
302
+ console.log(` [exit] handleExit called for ${shortId}... code=${code} isShuttingDown=${ctx.isShuttingDown}`);
303
+ if (!session) {
304
+ console.log(` [exit] Session ${shortId}... not found (already cleaned up)`);
305
+ return;
306
+ }
307
+ // If we're intentionally restarting (e.g., !cd), don't clean up
308
+ if (session.isRestarting) {
309
+ console.log(` [exit] Session ${shortId}... restarting, skipping cleanup`);
310
+ session.isRestarting = false;
311
+ return;
312
+ }
313
+ // If bot is shutting down, preserve persistence
314
+ if (ctx.isShuttingDown) {
315
+ console.log(` [exit] Session ${shortId}... bot shutting down, preserving persistence`);
316
+ ctx.stopTyping(session);
317
+ if (session.updateTimer) {
318
+ clearTimeout(session.updateTimer);
319
+ session.updateTimer = null;
320
+ }
321
+ ctx.sessions.delete(session.sessionId);
322
+ return;
323
+ }
324
+ // If session was interrupted, preserve for resume
325
+ if (session.wasInterrupted) {
326
+ console.log(` [exit] Session ${shortId}... exited after interrupt, preserving for resume`);
327
+ ctx.stopTyping(session);
328
+ if (session.updateTimer) {
329
+ clearTimeout(session.updateTimer);
330
+ session.updateTimer = null;
331
+ }
332
+ ctx.persistSession(session);
333
+ ctx.sessions.delete(session.sessionId);
334
+ // Clean up post index
335
+ for (const [postId, tid] of ctx.postIndex.entries()) {
336
+ if (tid === session.threadId) {
337
+ ctx.postIndex.delete(postId);
338
+ }
339
+ }
340
+ // Notify user
341
+ try {
342
+ await session.platform.createPost(`ℹ️ Session paused. Send a new message to continue.`, session.threadId);
343
+ }
344
+ catch {
345
+ // Ignore
346
+ }
347
+ console.log(` ⏸️ Session paused (${shortId}…) — ${ctx.sessions.size} active`);
348
+ return;
349
+ }
350
+ // For resumed sessions that exit with error, preserve for retry
351
+ if (session.isResumed && code !== 0) {
352
+ console.log(` [exit] Resumed session ${shortId}... failed with code ${code}, preserving for retry`);
353
+ ctx.stopTyping(session);
354
+ if (session.updateTimer) {
355
+ clearTimeout(session.updateTimer);
356
+ session.updateTimer = null;
357
+ }
358
+ ctx.sessions.delete(session.sessionId);
359
+ try {
360
+ await session.platform.createPost(`⚠️ **Session resume failed** (exit code ${code}). The session data is preserved - try restarting the bot.`, session.threadId);
361
+ }
362
+ catch {
363
+ // Ignore
364
+ }
365
+ return;
366
+ }
367
+ // Normal exit cleanup
368
+ console.log(` [exit] Session ${shortId}... normal exit, cleaning up`);
369
+ ctx.stopTyping(session);
370
+ if (session.updateTimer) {
371
+ clearTimeout(session.updateTimer);
372
+ session.updateTimer = null;
373
+ }
374
+ await ctx.flush(session);
375
+ if (code !== 0 && code !== null) {
376
+ await session.platform.createPost(`**[Exited: ${code}]**`, session.threadId);
377
+ }
378
+ // Clean up session from maps
379
+ ctx.sessions.delete(session.sessionId);
380
+ for (const [postId, tid] of ctx.postIndex.entries()) {
381
+ if (tid === session.threadId) {
382
+ ctx.postIndex.delete(postId);
383
+ }
384
+ }
385
+ // Only unpersist for normal exits
386
+ if (code === 0 || code === null) {
387
+ ctx.unpersistSession(session.sessionId);
388
+ }
389
+ else {
390
+ console.log(` [exit] Session ${shortId}... non-zero exit, preserving for potential retry`);
391
+ }
392
+ console.log(` ■ Session ended (${shortId}…) — ${ctx.sessions.size} active`);
393
+ }
394
+ /**
395
+ * Kill a specific session.
396
+ */
397
+ export function killSession(session, unpersist, ctx) {
398
+ const shortId = session.threadId.substring(0, 8);
399
+ // Set restarting flag to prevent handleExit from also unpersisting
400
+ if (!unpersist) {
401
+ session.isRestarting = true;
402
+ }
403
+ ctx.stopTyping(session);
404
+ session.claude.kill();
405
+ // Clean up session from maps
406
+ ctx.sessions.delete(session.sessionId);
407
+ for (const [postId, tid] of ctx.postIndex.entries()) {
408
+ if (tid === session.threadId) {
409
+ ctx.postIndex.delete(postId);
410
+ }
411
+ }
412
+ // Explicitly unpersist if requested
413
+ if (unpersist) {
414
+ ctx.unpersistSession(session.threadId);
415
+ }
416
+ console.log(` ✖ Session killed (${shortId}…) — ${ctx.sessions.size} active`);
417
+ }
418
+ /**
419
+ * Kill all active sessions.
420
+ */
421
+ export function killAllSessions(ctx) {
422
+ for (const session of ctx.sessions.values()) {
423
+ ctx.stopTyping(session);
424
+ session.claude.kill();
425
+ }
426
+ ctx.sessions.clear();
427
+ ctx.postIndex.clear();
428
+ }
429
+ // ---------------------------------------------------------------------------
430
+ // Cleanup
431
+ // ---------------------------------------------------------------------------
432
+ /**
433
+ * Clean up idle sessions that have timed out.
434
+ */
435
+ export function cleanupIdleSessions(timeoutMs, warningMs, ctx) {
436
+ const now = Date.now();
437
+ for (const [_sessionId, session] of ctx.sessions) {
438
+ const idleMs = now - session.lastActivityAt.getTime();
439
+ const shortId = session.threadId.substring(0, 8);
440
+ // Check for timeout
441
+ if (idleMs > timeoutMs) {
442
+ console.log(` ⏰ Session (${shortId}…) timed out after ${Math.round(idleMs / 60000)}min idle`);
443
+ session.platform.createPost(`⏰ **Session timed out** after ${Math.round(idleMs / 60000)} minutes of inactivity`, session.threadId).catch(() => { });
444
+ // Kill without unpersisting to allow resume
445
+ killSession(session, false, ctx);
446
+ continue;
447
+ }
448
+ // Check for warning threshold
449
+ if (idleMs > warningMs && !session.timeoutWarningPosted) {
450
+ const remainingMins = Math.round((timeoutMs - idleMs) / 60000);
451
+ session.platform.createPost(`⏰ **Session idle** - will timeout in ~${remainingMins} minutes without activity`, session.threadId).catch(() => { });
452
+ session.timeoutWarningPosted = true;
453
+ console.log(` ⏰ Session (${shortId}…) idle warning posted`);
454
+ }
455
+ }
456
+ }
@@ -0,0 +1,96 @@
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 type { PlatformClient, PlatformFile } from '../platform/index.js';
13
+ import { PersistedSession } from '../persistence/session-store.js';
14
+ import { WorktreeMode } from '../config.js';
15
+ /**
16
+ * SessionManager - Main orchestrator for Claude Code sessions
17
+ */
18
+ export declare class SessionManager {
19
+ private platforms;
20
+ private workingDir;
21
+ private skipPermissions;
22
+ private chromeEnabled;
23
+ private worktreeMode;
24
+ private debug;
25
+ private sessions;
26
+ private postIndex;
27
+ private sessionStore;
28
+ private cleanupTimer;
29
+ private isShuttingDown;
30
+ constructor(workingDir: string, skipPermissions?: boolean, chromeEnabled?: boolean, worktreeMode?: WorktreeMode);
31
+ addPlatform(platformId: string, client: PlatformClient): void;
32
+ removePlatform(platformId: string): void;
33
+ private getLifecycleContext;
34
+ private getEventContext;
35
+ private getReactionContext;
36
+ private getCommandContext;
37
+ private getSessionId;
38
+ private registerPost;
39
+ private getSessionByPost;
40
+ private handleMessage;
41
+ private handleReaction;
42
+ private handleSessionReaction;
43
+ private handleEvent;
44
+ private handleExit;
45
+ private appendContent;
46
+ private flush;
47
+ private startTyping;
48
+ private stopTyping;
49
+ private buildMessageContent;
50
+ private shouldPromptForWorktree;
51
+ private hasOtherSessionInRepo;
52
+ private postWorktreePrompt;
53
+ private persistSession;
54
+ private unpersistSession;
55
+ private updateSessionHeader;
56
+ initialize(): Promise<void>;
57
+ startSession(options: {
58
+ prompt: string;
59
+ files?: PlatformFile[];
60
+ }, username: string, replyToPostId?: string, platformId?: string): Promise<void>;
61
+ private findSessionByThreadId;
62
+ private findPersistedByThreadId;
63
+ sendFollowUp(threadId: string, message: string, files?: PlatformFile[]): Promise<void>;
64
+ isSessionActive(): boolean;
65
+ isInSessionThread(threadRoot: string): boolean;
66
+ hasPausedSession(threadId: string): boolean;
67
+ resumePausedSession(threadId: string, message: string, files?: PlatformFile[]): Promise<void>;
68
+ getPersistedSession(threadId: string): PersistedSession | undefined;
69
+ killSession(threadId: string, unpersist?: boolean): void;
70
+ killAllSessions(): void;
71
+ cancelSession(threadId: string, username: string): Promise<void>;
72
+ interruptSession(threadId: string, username: string): Promise<void>;
73
+ changeDirectory(threadId: string, newDir: string, username: string): Promise<void>;
74
+ inviteUser(threadId: string, invitedUser: string, invitedBy: string): Promise<void>;
75
+ kickUser(threadId: string, kickedUser: string, kickedBy: string): Promise<void>;
76
+ enableInteractivePermissions(threadId: string, username: string): Promise<void>;
77
+ isSessionInteractive(threadId: string): boolean;
78
+ requestMessageApproval(threadId: string, username: string, message: string): Promise<void>;
79
+ handleWorktreeBranchResponse(threadId: string, branchName: string, username: string): Promise<boolean>;
80
+ handleWorktreeSkip(threadId: string, username: string): Promise<void>;
81
+ createAndSwitchToWorktree(threadId: string, branch: string, username: string): Promise<void>;
82
+ switchToWorktree(threadId: string, branchOrPath: string, username: string): Promise<void>;
83
+ listWorktreesCommand(threadId: string, _username: string): Promise<void>;
84
+ removeWorktreeCommand(threadId: string, branchOrPath: string, username: string): Promise<void>;
85
+ disableWorktreePrompt(threadId: string, username: string): Promise<void>;
86
+ hasPendingWorktreePrompt(threadId: string): boolean;
87
+ getActiveThreadIds(): string[];
88
+ killAllSessionsAndUnpersist(): void;
89
+ isUserAllowedInSession(threadId: string, username: string): boolean;
90
+ startSessionWithWorktree(options: {
91
+ prompt: string;
92
+ files?: PlatformFile[];
93
+ }, branch: string, username: string, replyToPostId?: string, platformId?: string): Promise<void>;
94
+ setShuttingDown(): void;
95
+ shutdown(message?: string): Promise<void>;
96
+ }