claude-threads 0.12.1 → 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 (67) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +142 -37
  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 +39 -15
  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.d.ts +1 -1
  16. package/dist/onboarding.js +271 -69
  17. package/dist/persistence/session-store.d.ts +8 -2
  18. package/dist/persistence/session-store.js +41 -16
  19. package/dist/platform/client.d.ts +140 -0
  20. package/dist/platform/formatter.d.ts +74 -0
  21. package/dist/platform/index.d.ts +11 -0
  22. package/dist/platform/index.js +8 -0
  23. package/dist/platform/mattermost/client.d.ts +70 -0
  24. package/dist/{mattermost → platform/mattermost}/client.js +117 -34
  25. package/dist/platform/mattermost/formatter.d.ts +20 -0
  26. package/dist/platform/mattermost/formatter.js +46 -0
  27. package/dist/platform/mattermost/permission-api.d.ts +10 -0
  28. package/dist/platform/mattermost/permission-api.js +139 -0
  29. package/dist/platform/mattermost/types.js +1 -0
  30. package/dist/platform/permission-api-factory.d.ts +11 -0
  31. package/dist/platform/permission-api-factory.js +21 -0
  32. package/dist/platform/permission-api.d.ts +67 -0
  33. package/dist/platform/permission-api.js +8 -0
  34. package/dist/platform/types.d.ts +70 -0
  35. package/dist/platform/types.js +7 -0
  36. package/dist/session/commands.d.ts +52 -0
  37. package/dist/session/commands.js +323 -0
  38. package/dist/session/events.d.ts +25 -0
  39. package/dist/session/events.js +368 -0
  40. package/dist/session/index.d.ts +7 -0
  41. package/dist/session/index.js +6 -0
  42. package/dist/session/lifecycle.d.ts +70 -0
  43. package/dist/session/lifecycle.js +456 -0
  44. package/dist/session/manager.d.ts +96 -0
  45. package/dist/session/manager.js +537 -0
  46. package/dist/session/reactions.d.ts +25 -0
  47. package/dist/session/reactions.js +151 -0
  48. package/dist/session/streaming.d.ts +47 -0
  49. package/dist/session/streaming.js +152 -0
  50. package/dist/session/types.d.ts +78 -0
  51. package/dist/session/types.js +9 -0
  52. package/dist/session/worktree.d.ts +56 -0
  53. package/dist/session/worktree.js +339 -0
  54. package/dist/{mattermost → utils}/emoji.d.ts +3 -3
  55. package/dist/{mattermost → utils}/emoji.js +3 -3
  56. package/dist/utils/emoji.test.d.ts +1 -0
  57. package/dist/utils/tool-formatter.d.ts +10 -13
  58. package/dist/utils/tool-formatter.js +48 -43
  59. package/dist/utils/tool-formatter.test.js +67 -52
  60. package/package.json +2 -3
  61. package/dist/claude/session.d.ts +0 -256
  62. package/dist/claude/session.js +0 -1964
  63. package/dist/mattermost/client.d.ts +0 -56
  64. /package/dist/{mattermost/emoji.test.d.ts → platform/client.js} +0 -0
  65. /package/dist/{mattermost/types.js → platform/formatter.js} +0 -0
  66. /package/dist/{mattermost → platform/mattermost}/types.d.ts +0 -0
  67. /package/dist/{mattermost → utils}/emoji.test.js +0 -0
@@ -1,5 +1,20 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import { shortenPath, parseMcpToolName, formatToolUse, formatToolForPermission, } from './tool-formatter.js';
3
+ // Mock formatter for tests - uses standard markdown syntax
4
+ const formatter = {
5
+ formatBold: (text) => `**${text}**`,
6
+ formatItalic: (text) => `_${text}_`,
7
+ formatCode: (text) => `\`${text}\``,
8
+ formatCodeBlock: (code, language) => language ? `\`\`\`${language}\n${code}\n\`\`\`` : `\`\`\`\n${code}\n\`\`\``,
9
+ formatUserMention: (username) => `@${username}`,
10
+ formatLink: (text, url) => `[${text}](${url})`,
11
+ formatListItem: (text) => `- ${text}`,
12
+ formatNumberedListItem: (num, text) => `${num}. ${text}`,
13
+ formatBlockquote: (text) => `> ${text}`,
14
+ formatHorizontalRule: () => '---',
15
+ formatHeading: (text, level) => `${'#'.repeat(level)} ${text}`,
16
+ escapeText: (text) => text,
17
+ };
3
18
  describe('shortenPath', () => {
4
19
  const originalHome = process.env.HOME;
5
20
  beforeEach(() => {
@@ -65,11 +80,11 @@ describe('formatToolUse', () => {
65
80
  it('formats Read with file path', () => {
66
81
  const result = formatToolUse('Read', {
67
82
  file_path: '/Users/testuser/file.ts',
68
- });
83
+ }, formatter);
69
84
  expect(result).toBe('📄 **Read** `~/file.ts`');
70
85
  });
71
86
  it('shows full path when not under home', () => {
72
- const result = formatToolUse('Read', { file_path: '/var/log/app.log' });
87
+ const result = formatToolUse('Read', { file_path: '/var/log/app.log' }, formatter);
73
88
  expect(result).toBe('📄 **Read** `/var/log/app.log`');
74
89
  });
75
90
  });
@@ -79,7 +94,7 @@ describe('formatToolUse', () => {
79
94
  file_path: '/Users/testuser/file.ts',
80
95
  old_string: 'old',
81
96
  new_string: 'new',
82
- });
97
+ }, formatter);
83
98
  expect(result).toBe('✏️ **Edit** `~/file.ts`');
84
99
  });
85
100
  it('formats Edit with diff in detailed mode', () => {
@@ -87,7 +102,7 @@ describe('formatToolUse', () => {
87
102
  file_path: '/Users/testuser/file.ts',
88
103
  old_string: 'old line',
89
104
  new_string: 'new line',
90
- }, { detailed: true });
105
+ }, formatter, { detailed: true });
91
106
  expect(result).toContain('✏️ **Edit** `~/file.ts`');
92
107
  expect(result).toContain('```diff');
93
108
  expect(result).toContain('- old line');
@@ -100,7 +115,7 @@ describe('formatToolUse', () => {
100
115
  file_path: '/Users/testuser/file.ts',
101
116
  old_string: oldLines,
102
117
  new_string: newLines,
103
- }, { detailed: true, maxPreviewLines: 10 });
118
+ }, formatter, { detailed: true, maxPreviewLines: 10 });
104
119
  expect(result).toContain('more lines');
105
120
  });
106
121
  });
@@ -109,16 +124,16 @@ describe('formatToolUse', () => {
109
124
  const result = formatToolUse('Write', {
110
125
  file_path: '/Users/testuser/file.ts',
111
126
  content: 'hello world',
112
- });
127
+ }, formatter);
113
128
  expect(result).toBe('📝 **Write** `~/file.ts`');
114
129
  });
115
130
  it('formats Write with preview in detailed mode', () => {
116
131
  const result = formatToolUse('Write', {
117
132
  file_path: '/Users/testuser/file.ts',
118
133
  content: 'line 1\nline 2\nline 3',
119
- }, { detailed: true });
134
+ }, formatter, { detailed: true });
120
135
  expect(result).toContain('📝 **Write** `~/file.ts`');
121
- expect(result).toContain('*(3 lines)*');
136
+ expect(result).toContain('_(3 lines)_');
122
137
  expect(result).toContain('line 1');
123
138
  });
124
139
  it('truncates long content previews', () => {
@@ -126,189 +141,189 @@ describe('formatToolUse', () => {
126
141
  const result = formatToolUse('Write', {
127
142
  file_path: '/Users/testuser/file.ts',
128
143
  content,
129
- }, { detailed: true });
144
+ }, formatter, { detailed: true });
130
145
  expect(result).toContain('more lines');
131
146
  });
132
147
  });
133
148
  describe('Bash tool', () => {
134
149
  it('formats short commands', () => {
135
- const result = formatToolUse('Bash', { command: 'ls -la' });
150
+ const result = formatToolUse('Bash', { command: 'ls -la' }, formatter);
136
151
  expect(result).toBe('💻 **Bash** `ls -la`');
137
152
  });
138
153
  it('truncates long commands', () => {
139
154
  const longCmd = 'x'.repeat(100);
140
- const result = formatToolUse('Bash', { command: longCmd });
155
+ const result = formatToolUse('Bash', { command: longCmd }, formatter);
141
156
  expect(result).not.toBeNull();
142
157
  expect(result).toContain('...');
143
158
  expect(result.length).toBeLessThan(120);
144
159
  });
145
160
  it('respects custom maxCommandLength', () => {
146
- const result = formatToolUse('Bash', { command: '1234567890' }, { maxCommandLength: 5 });
161
+ const result = formatToolUse('Bash', { command: '1234567890' }, formatter, { maxCommandLength: 5 });
147
162
  expect(result).toBe('💻 **Bash** `12345...`');
148
163
  });
149
164
  });
150
165
  describe('Other tools', () => {
151
166
  it('formats Glob', () => {
152
- const result = formatToolUse('Glob', { pattern: '**/*.ts' });
167
+ const result = formatToolUse('Glob', { pattern: '**/*.ts' }, formatter);
153
168
  expect(result).toBe('🔍 **Glob** `**/*.ts`');
154
169
  });
155
170
  it('formats Grep', () => {
156
- const result = formatToolUse('Grep', { pattern: 'TODO' });
171
+ const result = formatToolUse('Grep', { pattern: 'TODO' }, formatter);
157
172
  expect(result).toBe('🔎 **Grep** `TODO`');
158
173
  });
159
174
  it('formats EnterPlanMode', () => {
160
- const result = formatToolUse('EnterPlanMode', {});
175
+ const result = formatToolUse('EnterPlanMode', {}, formatter);
161
176
  expect(result).toBe('📋 **Planning...**');
162
177
  });
163
178
  it('formats WebFetch', () => {
164
179
  const result = formatToolUse('WebFetch', {
165
180
  url: 'https://example.com/page',
166
- });
181
+ }, formatter);
167
182
  expect(result).toBe('🌐 **Fetching** `https://example.com/page`');
168
183
  });
169
184
  it('formats WebSearch', () => {
170
- const result = formatToolUse('WebSearch', { query: 'typescript guide' });
185
+ const result = formatToolUse('WebSearch', { query: 'typescript guide' }, formatter);
171
186
  expect(result).toBe('🔍 **Searching** `typescript guide`');
172
187
  });
173
188
  });
174
189
  describe('Tools that return null', () => {
175
190
  it('returns null for Task', () => {
176
- expect(formatToolUse('Task', {})).toBeNull();
191
+ expect(formatToolUse('Task', {}, formatter)).toBeNull();
177
192
  });
178
193
  it('returns null for ExitPlanMode', () => {
179
- expect(formatToolUse('ExitPlanMode', {})).toBeNull();
194
+ expect(formatToolUse('ExitPlanMode', {}, formatter)).toBeNull();
180
195
  });
181
196
  it('returns null for AskUserQuestion', () => {
182
- expect(formatToolUse('AskUserQuestion', {})).toBeNull();
197
+ expect(formatToolUse('AskUserQuestion', {}, formatter)).toBeNull();
183
198
  });
184
199
  it('returns null for TodoWrite', () => {
185
- expect(formatToolUse('TodoWrite', {})).toBeNull();
200
+ expect(formatToolUse('TodoWrite', {}, formatter)).toBeNull();
186
201
  });
187
202
  });
188
203
  describe('MCP tools', () => {
189
204
  it('formats MCP tools', () => {
190
- const result = formatToolUse('mcp__myserver__mytool', { arg: 'value' });
191
- expect(result).toBe('🔌 **mytool** *(myserver)*');
205
+ const result = formatToolUse('mcp__myserver__mytool', { arg: 'value' }, formatter);
206
+ expect(result).toBe('🔌 **mytool** _(myserver)_');
192
207
  });
193
208
  it('formats MCP tools with underscores in name', () => {
194
- const result = formatToolUse('mcp__my_server__my_tool', { arg: 'value' });
195
- expect(result).toBe('🔌 **my_tool** *(my_server)*');
209
+ const result = formatToolUse('mcp__my_server__my_tool', { arg: 'value' }, formatter);
210
+ expect(result).toBe('🔌 **my_tool** _(my_server)_');
196
211
  });
197
212
  });
198
213
  describe('Claude in Chrome tools', () => {
199
214
  it('formats computer screenshot action', () => {
200
- const result = formatToolUse('mcp__claude-in-chrome__computer', { action: 'screenshot' });
215
+ const result = formatToolUse('mcp__claude-in-chrome__computer', { action: 'screenshot' }, formatter);
201
216
  expect(result).toBe('🌐 **Chrome**[computer] `screenshot`');
202
217
  });
203
218
  it('formats computer click actions with coordinates', () => {
204
219
  const result = formatToolUse('mcp__claude-in-chrome__computer', {
205
220
  action: 'left_click',
206
221
  coordinate: [100, 200],
207
- });
222
+ }, formatter);
208
223
  expect(result).toBe('🌐 **Chrome**[computer] `left_click at (100, 200)`');
209
224
  });
210
225
  it('formats computer type action', () => {
211
226
  const result = formatToolUse('mcp__claude-in-chrome__computer', {
212
227
  action: 'type',
213
228
  text: 'hello world',
214
- });
229
+ }, formatter);
215
230
  expect(result).toBe('🌐 **Chrome**[computer] `type "hello world"`');
216
231
  });
217
232
  it('truncates long type text', () => {
218
233
  const result = formatToolUse('mcp__claude-in-chrome__computer', {
219
234
  action: 'type',
220
235
  text: 'this is a very long text that should be truncated',
221
- });
236
+ }, formatter);
222
237
  expect(result).toContain('...');
223
238
  });
224
239
  it('formats computer key action', () => {
225
240
  const result = formatToolUse('mcp__claude-in-chrome__computer', {
226
241
  action: 'key',
227
242
  text: 'Enter',
228
- });
243
+ }, formatter);
229
244
  expect(result).toBe('🌐 **Chrome**[computer] `key Enter`');
230
245
  });
231
246
  it('formats computer scroll action', () => {
232
247
  const result = formatToolUse('mcp__claude-in-chrome__computer', {
233
248
  action: 'scroll',
234
249
  scroll_direction: 'up',
235
- });
250
+ }, formatter);
236
251
  expect(result).toBe('🌐 **Chrome**[computer] `scroll up`');
237
252
  });
238
253
  it('formats computer wait action', () => {
239
254
  const result = formatToolUse('mcp__claude-in-chrome__computer', {
240
255
  action: 'wait',
241
256
  duration: 2,
242
- });
257
+ }, formatter);
243
258
  expect(result).toBe('🌐 **Chrome**[computer] `wait 2s`');
244
259
  });
245
260
  it('formats navigate tool', () => {
246
261
  const result = formatToolUse('mcp__claude-in-chrome__navigate', {
247
262
  url: 'https://example.com/page',
248
- });
263
+ }, formatter);
249
264
  expect(result).toBe('🌐 **Chrome**[navigate] `https://example.com/page`');
250
265
  });
251
266
  it('truncates long URLs in navigate', () => {
252
267
  const result = formatToolUse('mcp__claude-in-chrome__navigate', {
253
268
  url: 'https://example.com/' + 'x'.repeat(100),
254
- });
269
+ }, formatter);
255
270
  expect(result).toContain('...');
256
271
  });
257
272
  it('formats tabs_context_mcp tool', () => {
258
- const result = formatToolUse('mcp__claude-in-chrome__tabs_context_mcp', {});
273
+ const result = formatToolUse('mcp__claude-in-chrome__tabs_context_mcp', {}, formatter);
259
274
  expect(result).toBe('🌐 **Chrome**[tabs] reading context');
260
275
  });
261
276
  it('formats tabs_create_mcp tool', () => {
262
- const result = formatToolUse('mcp__claude-in-chrome__tabs_create_mcp', {});
277
+ const result = formatToolUse('mcp__claude-in-chrome__tabs_create_mcp', {}, formatter);
263
278
  expect(result).toBe('🌐 **Chrome**[tabs] creating new tab');
264
279
  });
265
280
  it('formats read_page tool', () => {
266
- const result = formatToolUse('mcp__claude-in-chrome__read_page', {});
281
+ const result = formatToolUse('mcp__claude-in-chrome__read_page', {}, formatter);
267
282
  expect(result).toBe('🌐 **Chrome**[read_page] accessibility tree');
268
283
  });
269
284
  it('formats read_page tool with interactive filter', () => {
270
285
  const result = formatToolUse('mcp__claude-in-chrome__read_page', {
271
286
  filter: 'interactive',
272
- });
287
+ }, formatter);
273
288
  expect(result).toBe('🌐 **Chrome**[read_page] interactive elements');
274
289
  });
275
290
  it('formats find tool', () => {
276
291
  const result = formatToolUse('mcp__claude-in-chrome__find', {
277
292
  query: 'login button',
278
- });
293
+ }, formatter);
279
294
  expect(result).toBe('🌐 **Chrome**[find] `login button`');
280
295
  });
281
296
  it('formats form_input tool', () => {
282
297
  const result = formatToolUse('mcp__claude-in-chrome__form_input', {
283
298
  ref: 'ref_1',
284
299
  value: 'test',
285
- });
300
+ }, formatter);
286
301
  expect(result).toBe('🌐 **Chrome**[form_input] setting value');
287
302
  });
288
303
  it('formats get_page_text tool', () => {
289
- const result = formatToolUse('mcp__claude-in-chrome__get_page_text', {});
304
+ const result = formatToolUse('mcp__claude-in-chrome__get_page_text', {}, formatter);
290
305
  expect(result).toBe('🌐 **Chrome**[get_page_text] extracting content');
291
306
  });
292
307
  it('formats javascript_tool', () => {
293
308
  const result = formatToolUse('mcp__claude-in-chrome__javascript_tool', {
294
309
  text: 'document.title',
295
- });
310
+ }, formatter);
296
311
  expect(result).toBe('🌐 **Chrome**[javascript] executing script');
297
312
  });
298
313
  it('formats gif_creator tool', () => {
299
314
  const result = formatToolUse('mcp__claude-in-chrome__gif_creator', {
300
315
  action: 'start_recording',
301
- });
316
+ }, formatter);
302
317
  expect(result).toBe('🌐 **Chrome**[gif] start_recording');
303
318
  });
304
319
  it('formats unknown Chrome tools', () => {
305
- const result = formatToolUse('mcp__claude-in-chrome__new_tool', {});
320
+ const result = formatToolUse('mcp__claude-in-chrome__new_tool', {}, formatter);
306
321
  expect(result).toBe('🌐 **Chrome**[new_tool]');
307
322
  });
308
323
  });
309
324
  describe('Unknown tools', () => {
310
325
  it('formats unknown tools with bullet', () => {
311
- const result = formatToolUse('CustomTool', {});
326
+ const result = formatToolUse('CustomTool', {}, formatter);
312
327
  expect(result).toBe('● **CustomTool**');
313
328
  });
314
329
  });
@@ -324,34 +339,34 @@ describe('formatToolForPermission', () => {
324
339
  it('formats Read tool', () => {
325
340
  const result = formatToolForPermission('Read', {
326
341
  file_path: '/Users/testuser/file.ts',
327
- });
342
+ }, formatter);
328
343
  expect(result).toBe('📄 **Read** `~/file.ts`');
329
344
  });
330
345
  it('formats Write tool', () => {
331
346
  const result = formatToolForPermission('Write', {
332
347
  file_path: '/Users/testuser/file.ts',
333
- });
348
+ }, formatter);
334
349
  expect(result).toBe('📝 **Write** `~/file.ts`');
335
350
  });
336
351
  it('formats Edit tool', () => {
337
352
  const result = formatToolForPermission('Edit', {
338
353
  file_path: '/Users/testuser/file.ts',
339
- });
354
+ }, formatter);
340
355
  expect(result).toBe('✏️ **Edit** `~/file.ts`');
341
356
  });
342
357
  it('formats Bash with longer truncation limit (100 chars)', () => {
343
358
  const cmd = 'x'.repeat(100);
344
- const result = formatToolForPermission('Bash', { command: cmd });
359
+ const result = formatToolForPermission('Bash', { command: cmd }, formatter);
345
360
  // Should truncate at 100
346
361
  expect(result).toContain('...');
347
362
  expect(result.length).toBeLessThan(150);
348
363
  });
349
364
  it('formats MCP tools', () => {
350
- const result = formatToolForPermission('mcp__server__tool', {});
351
- expect(result).toBe('🔌 **tool** *(server)*');
365
+ const result = formatToolForPermission('mcp__server__tool', {}, formatter);
366
+ expect(result).toBe('🔌 **tool** _(server)_');
352
367
  });
353
368
  it('formats unknown tools', () => {
354
- const result = formatToolForPermission('CustomTool', {});
369
+ const result = formatToolForPermission('CustomTool', {}, formatter);
355
370
  expect(result).toBe('● **CustomTool**');
356
371
  });
357
372
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
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",
@@ -51,15 +51,14 @@
51
51
  "@modelcontextprotocol/sdk": "^1.25.1",
52
52
  "commander": "^14.0.2",
53
53
  "diff": "^8.0.2",
54
- "dotenv": "^16.4.7",
55
54
  "prompts": "^2.4.2",
56
55
  "update-notifier": "^7.3.1",
57
56
  "ws": "^8.18.0",
57
+ "yaml": "^2.8.2",
58
58
  "zod": "^4.2.1"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@eslint/js": "^9.39.2",
62
- "@types/diff": "^7.0.2",
63
62
  "@types/node": "^22.19.3",
64
63
  "@types/prompts": "^2.4.9",
65
64
  "@types/update-notifier": "^6.0.8",
@@ -1,256 +0,0 @@
1
- import { ClaudeCli } from './cli.js';
2
- import { MattermostClient } from '../mattermost/client.js';
3
- import { MattermostFile } from '../mattermost/types.js';
4
- import { PersistedSession, WorktreeInfo } from '../persistence/session-store.js';
5
- import { WorktreeMode } from '../config.js';
6
- interface QuestionOption {
7
- label: string;
8
- description: string;
9
- }
10
- interface PendingQuestionSet {
11
- toolUseId: string;
12
- currentIndex: number;
13
- currentPostId: string | null;
14
- questions: Array<{
15
- header: string;
16
- question: string;
17
- options: QuestionOption[];
18
- answer: string | null;
19
- }>;
20
- }
21
- interface PendingApproval {
22
- postId: string;
23
- type: 'plan' | 'action';
24
- toolUseId: string;
25
- }
26
- /**
27
- * Pending message from unauthorized user awaiting approval
28
- */
29
- interface PendingMessageApproval {
30
- postId: string;
31
- originalMessage: string;
32
- fromUser: string;
33
- }
34
- /**
35
- * Represents a single Claude Code session tied to a Mattermost thread.
36
- * Each session has its own Claude CLI process and state.
37
- */
38
- interface Session {
39
- threadId: string;
40
- claudeSessionId: string;
41
- startedBy: string;
42
- startedAt: Date;
43
- lastActivityAt: Date;
44
- sessionNumber: number;
45
- workingDir: string;
46
- claude: ClaudeCli;
47
- currentPostId: string | null;
48
- pendingContent: string;
49
- pendingApproval: PendingApproval | null;
50
- pendingQuestionSet: PendingQuestionSet | null;
51
- pendingMessageApproval: PendingMessageApproval | null;
52
- planApproved: boolean;
53
- sessionAllowedUsers: Set<string>;
54
- forceInteractivePermissions: boolean;
55
- sessionStartPostId: string | null;
56
- tasksPostId: string | null;
57
- activeSubagents: Map<string, string>;
58
- updateTimer: ReturnType<typeof setTimeout> | null;
59
- typingTimer: ReturnType<typeof setInterval> | null;
60
- timeoutWarningPosted: boolean;
61
- isRestarting: boolean;
62
- isResumed: boolean;
63
- wasInterrupted: boolean;
64
- inProgressTaskStart: number | null;
65
- activeToolStarts: Map<string, number>;
66
- worktreeInfo?: WorktreeInfo;
67
- pendingWorktreePrompt?: boolean;
68
- worktreePromptDisabled?: boolean;
69
- queuedPrompt?: string;
70
- worktreePromptPostId?: string;
71
- }
72
- export declare class SessionManager {
73
- private mattermost;
74
- private workingDir;
75
- private skipPermissions;
76
- private chromeEnabled;
77
- private worktreeMode;
78
- private debug;
79
- private sessions;
80
- private postIndex;
81
- private sessionStore;
82
- private cleanupTimer;
83
- private isShuttingDown;
84
- constructor(mattermost: MattermostClient, workingDir: string, skipPermissions?: boolean, chromeEnabled?: boolean, worktreeMode?: WorktreeMode);
85
- /**
86
- * Initialize session manager by resuming any persisted sessions.
87
- * Should be called before starting to listen for new messages.
88
- */
89
- initialize(): Promise<void>;
90
- /**
91
- * Resume a single session from persisted state
92
- */
93
- private resumeSession;
94
- /**
95
- * Persist a session to disk
96
- */
97
- private persistSession;
98
- /**
99
- * Remove a session from persistence
100
- */
101
- private unpersistSession;
102
- /** Get a session by thread ID */
103
- getSession(threadId: string): Session | undefined;
104
- /** Check if a session exists for this thread */
105
- hasSession(threadId: string): boolean;
106
- /** Get the number of active sessions */
107
- getSessionCount(): number;
108
- /** Get all active session thread IDs */
109
- getActiveThreadIds(): string[];
110
- /** Mark that we're shutting down (prevents cleanup of persisted sessions) */
111
- setShuttingDown(): void;
112
- /** Register a post for reaction routing */
113
- private registerPost;
114
- /** Find session by post ID (for reaction routing) */
115
- private getSessionByPost;
116
- /**
117
- * Check if a user is allowed in a specific session.
118
- * Checks global allowlist first, then session-specific allowlist.
119
- */
120
- isUserAllowedInSession(threadId: string, username: string): boolean;
121
- startSession(options: {
122
- prompt: string;
123
- files?: MattermostFile[];
124
- }, username: string, replyToPostId?: string): Promise<void>;
125
- /**
126
- * Start a session with an initial worktree specified.
127
- * Used when user specifies "on branch X" or "!worktree X" in their initial message.
128
- */
129
- startSessionWithWorktree(options: {
130
- prompt: string;
131
- files?: MattermostFile[];
132
- }, branch: string, username: string, replyToPostId?: string): Promise<void>;
133
- /**
134
- * Check if we should prompt for a worktree before starting work.
135
- * Returns the reason string if we should prompt, or null if not.
136
- */
137
- private shouldPromptForWorktree;
138
- /**
139
- * Check if another session is using the same repository
140
- */
141
- private hasOtherSessionInRepo;
142
- /**
143
- * Post the worktree prompt message
144
- */
145
- private postWorktreePrompt;
146
- private handleEvent;
147
- private handleTaskComplete;
148
- private handleExitPlanMode;
149
- private handleTodoWrite;
150
- private handleTaskStart;
151
- private handleAskUserQuestion;
152
- private postCurrentQuestion;
153
- private handleReaction;
154
- private handleQuestionReaction;
155
- private handleApprovalReaction;
156
- private handleMessageApprovalReaction;
157
- private formatEvent;
158
- private appendContent;
159
- private scheduleUpdate;
160
- /**
161
- * Build message content for Claude, including images if present.
162
- * Returns either a string or an array of content blocks.
163
- */
164
- private buildMessageContent;
165
- private startTyping;
166
- private stopTyping;
167
- private flush;
168
- private handleExit;
169
- /** Check if any sessions are active */
170
- isSessionActive(): boolean;
171
- /** Check if a session exists for this thread */
172
- isInSessionThread(threadRoot: string): boolean;
173
- /** Send a follow-up message to an existing session */
174
- sendFollowUp(threadId: string, message: string, files?: MattermostFile[]): Promise<void>;
175
- /**
176
- * Check if there's a paused (persisted but not active) session for this thread.
177
- * This is used to detect when we should resume a session instead of ignoring the message.
178
- */
179
- hasPausedSession(threadId: string): boolean;
180
- /**
181
- * Resume a paused session and send a message to it.
182
- * Called when a user sends a message to a thread with a paused session.
183
- */
184
- resumePausedSession(threadId: string, message: string, files?: MattermostFile[]): Promise<void>;
185
- /**
186
- * Get persisted session info for access control checks
187
- */
188
- getPersistedSession(threadId: string): PersistedSession | undefined;
189
- /** Kill a specific session */
190
- killSession(threadId: string, unpersist?: boolean): void;
191
- /** Cancel a session with user feedback */
192
- cancelSession(threadId: string, username: string): Promise<void>;
193
- /** Interrupt current processing but keep session alive (like Escape in CLI) */
194
- interruptSession(threadId: string, username: string): Promise<void>;
195
- /** Change working directory for a session (restarts Claude CLI) */
196
- changeDirectory(threadId: string, newDir: string, username: string): Promise<void>;
197
- /** Invite a user to participate in a specific session */
198
- inviteUser(threadId: string, invitedUser: string, invitedBy: string): Promise<void>;
199
- /** Kick a user from a specific session */
200
- kickUser(threadId: string, kickedUser: string, kickedBy: string): Promise<void>;
201
- /**
202
- * Enable interactive permissions for a session.
203
- * Can only downgrade (skip → interactive), not upgrade.
204
- */
205
- enableInteractivePermissions(threadId: string, username: string): Promise<void>;
206
- /** Check if a session should use interactive permissions */
207
- isSessionInteractive(threadId: string): boolean;
208
- /** Update the session header post with current participants */
209
- private updateSessionHeader;
210
- /** Request approval for a message from an unauthorized user */
211
- requestMessageApproval(threadId: string, username: string, message: string): Promise<void>;
212
- /**
213
- * Handle a worktree branch response from user.
214
- * Called when user replies with a branch name to the worktree prompt.
215
- */
216
- handleWorktreeBranchResponse(threadId: string, branchName: string, username: string): Promise<boolean>;
217
- /**
218
- * Handle ❌ reaction on worktree prompt - skip worktree and continue in main repo.
219
- */
220
- handleWorktreeSkip(threadId: string, username: string): Promise<void>;
221
- /**
222
- * Create a new worktree and switch the session to it.
223
- */
224
- createAndSwitchToWorktree(threadId: string, branch: string, username: string): Promise<void>;
225
- /**
226
- * Switch to an existing worktree.
227
- */
228
- switchToWorktree(threadId: string, branchOrPath: string, username: string): Promise<void>;
229
- /**
230
- * List all worktrees for the current repository.
231
- */
232
- listWorktreesCommand(threadId: string, _username: string): Promise<void>;
233
- /**
234
- * Remove a worktree.
235
- */
236
- removeWorktreeCommand(threadId: string, branchOrPath: string, username: string): Promise<void>;
237
- /**
238
- * Disable worktree prompts for a session.
239
- */
240
- disableWorktreePrompt(threadId: string, username: string): Promise<void>;
241
- /**
242
- * Check if a session has a pending worktree prompt.
243
- */
244
- hasPendingWorktreePrompt(threadId: string): boolean;
245
- /**
246
- * Get the worktree prompt post ID for a session.
247
- */
248
- getWorktreePromptPostId(threadId: string): string | undefined;
249
- /** Kill all active sessions (for graceful shutdown) */
250
- killAllSessions(): void;
251
- /** Kill all sessions AND unpersist them (for emergency shutdown - no resume) */
252
- killAllSessionsAndUnpersist(): void;
253
- /** Cleanup idle sessions that have exceeded timeout */
254
- private cleanupIdleSessions;
255
- }
256
- export {};