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.
- package/CHANGELOG.md +53 -0
- package/README.md +142 -37
- package/dist/claude/cli.d.ts +8 -0
- package/dist/claude/cli.js +16 -8
- package/dist/config/migration.d.ts +45 -0
- package/dist/config/migration.js +35 -0
- package/dist/config.d.ts +12 -18
- package/dist/config.js +7 -94
- package/dist/git/worktree.d.ts +0 -4
- package/dist/git/worktree.js +1 -1
- package/dist/index.js +39 -15
- package/dist/logo.d.ts +3 -20
- package/dist/logo.js +7 -23
- package/dist/mcp/permission-server.js +61 -112
- package/dist/onboarding.d.ts +1 -1
- package/dist/onboarding.js +271 -69
- package/dist/persistence/session-store.d.ts +8 -2
- package/dist/persistence/session-store.js +41 -16
- package/dist/platform/client.d.ts +140 -0
- package/dist/platform/formatter.d.ts +74 -0
- package/dist/platform/index.d.ts +11 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/mattermost/client.d.ts +70 -0
- package/dist/{mattermost → platform/mattermost}/client.js +117 -34
- package/dist/platform/mattermost/formatter.d.ts +20 -0
- package/dist/platform/mattermost/formatter.js +46 -0
- package/dist/platform/mattermost/permission-api.d.ts +10 -0
- package/dist/platform/mattermost/permission-api.js +139 -0
- package/dist/platform/mattermost/types.js +1 -0
- package/dist/platform/permission-api-factory.d.ts +11 -0
- package/dist/platform/permission-api-factory.js +21 -0
- package/dist/platform/permission-api.d.ts +67 -0
- package/dist/platform/permission-api.js +8 -0
- package/dist/platform/types.d.ts +70 -0
- package/dist/platform/types.js +7 -0
- package/dist/session/commands.d.ts +52 -0
- package/dist/session/commands.js +323 -0
- package/dist/session/events.d.ts +25 -0
- package/dist/session/events.js +368 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +6 -0
- package/dist/session/lifecycle.d.ts +70 -0
- package/dist/session/lifecycle.js +456 -0
- package/dist/session/manager.d.ts +96 -0
- package/dist/session/manager.js +537 -0
- package/dist/session/reactions.d.ts +25 -0
- package/dist/session/reactions.js +151 -0
- package/dist/session/streaming.d.ts +47 -0
- package/dist/session/streaming.js +152 -0
- package/dist/session/types.d.ts +78 -0
- package/dist/session/types.js +9 -0
- package/dist/session/worktree.d.ts +56 -0
- package/dist/session/worktree.js +339 -0
- package/dist/{mattermost → utils}/emoji.d.ts +3 -3
- package/dist/{mattermost → utils}/emoji.js +3 -3
- package/dist/utils/emoji.test.d.ts +1 -0
- package/dist/utils/tool-formatter.d.ts +10 -13
- package/dist/utils/tool-formatter.js +48 -43
- package/dist/utils/tool-formatter.test.js +67 -52
- package/package.json +2 -3
- package/dist/claude/session.d.ts +0 -256
- package/dist/claude/session.js +0 -1964
- package/dist/mattermost/client.d.ts +0 -56
- /package/dist/{mattermost/emoji.test.d.ts → platform/client.js} +0 -0
- /package/dist/{mattermost/types.js → platform/formatter.js} +0 -0
- /package/dist/{mattermost → platform/mattermost}/types.d.ts +0 -0
- /package/dist/{mattermost → utils}/emoji.test.js +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude event handling module
|
|
3
|
+
*
|
|
4
|
+
* Handles events from Claude CLI: assistant messages, tool use,
|
|
5
|
+
* tool results, tasks, questions, and plan approvals.
|
|
6
|
+
*/
|
|
7
|
+
import { formatToolUse as sharedFormatToolUse } from '../utils/tool-formatter.js';
|
|
8
|
+
import { NUMBER_EMOJIS, APPROVAL_EMOJIS, DENIAL_EMOJIS, } from '../utils/emoji.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Main event handler
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Handle a Claude event from the CLI stream.
|
|
14
|
+
* Routes to appropriate handler based on event type.
|
|
15
|
+
*/
|
|
16
|
+
export function handleEvent(session, event, ctx) {
|
|
17
|
+
// Update last activity and reset timeout warning
|
|
18
|
+
session.lastActivityAt = new Date();
|
|
19
|
+
session.timeoutWarningPosted = false;
|
|
20
|
+
// Check for special tool uses that need custom handling
|
|
21
|
+
if (event.type === 'assistant') {
|
|
22
|
+
const msg = event.message;
|
|
23
|
+
let hasSpecialTool = false;
|
|
24
|
+
for (const block of msg?.content || []) {
|
|
25
|
+
if (block.type === 'tool_use') {
|
|
26
|
+
if (block.name === 'ExitPlanMode') {
|
|
27
|
+
handleExitPlanMode(session, block.id, ctx);
|
|
28
|
+
hasSpecialTool = true;
|
|
29
|
+
}
|
|
30
|
+
else if (block.name === 'TodoWrite') {
|
|
31
|
+
handleTodoWrite(session, block.input);
|
|
32
|
+
}
|
|
33
|
+
else if (block.name === 'Task') {
|
|
34
|
+
handleTaskStart(session, block.id, block.input);
|
|
35
|
+
}
|
|
36
|
+
else if (block.name === 'AskUserQuestion') {
|
|
37
|
+
handleAskUserQuestion(session, block.id, block.input, ctx);
|
|
38
|
+
hasSpecialTool = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (hasSpecialTool)
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Check for tool_result to update subagent status
|
|
46
|
+
if (event.type === 'user') {
|
|
47
|
+
const msg = event.message;
|
|
48
|
+
for (const block of msg?.content || []) {
|
|
49
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
50
|
+
const postId = session.activeSubagents.get(block.tool_use_id);
|
|
51
|
+
if (postId) {
|
|
52
|
+
handleTaskComplete(session, block.tool_use_id, postId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const formatted = formatEvent(session, event, ctx);
|
|
58
|
+
if (ctx.debug) {
|
|
59
|
+
console.log(`[DEBUG] handleEvent(${session.threadId}): ${event.type} -> ${formatted ? formatted.substring(0, 100) : '(null)'}`);
|
|
60
|
+
}
|
|
61
|
+
if (formatted)
|
|
62
|
+
ctx.appendContent(session, formatted);
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Event formatters
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/**
|
|
68
|
+
* Format a Claude event for display in chat platforms.
|
|
69
|
+
*/
|
|
70
|
+
function formatEvent(session, e, ctx) {
|
|
71
|
+
switch (e.type) {
|
|
72
|
+
case 'assistant': {
|
|
73
|
+
const msg = e.message;
|
|
74
|
+
const parts = [];
|
|
75
|
+
for (const block of msg?.content || []) {
|
|
76
|
+
if (block.type === 'text' && block.text) {
|
|
77
|
+
// Filter out <thinking> tags that may appear in text content
|
|
78
|
+
const text = block.text.replace(/<thinking>[\s\S]*?<\/thinking>/g, '').trim();
|
|
79
|
+
if (text)
|
|
80
|
+
parts.push(text);
|
|
81
|
+
}
|
|
82
|
+
else if (block.type === 'tool_use' && block.name) {
|
|
83
|
+
const formatted = sharedFormatToolUse(block.name, block.input || {}, session.platform.getFormatter(), { detailed: true });
|
|
84
|
+
if (formatted)
|
|
85
|
+
parts.push(formatted);
|
|
86
|
+
}
|
|
87
|
+
else if (block.type === 'thinking' && block.thinking) {
|
|
88
|
+
// Extended thinking - show abbreviated version
|
|
89
|
+
const thinking = block.thinking;
|
|
90
|
+
const preview = thinking.length > 100 ? thinking.substring(0, 100) + '...' : thinking;
|
|
91
|
+
parts.push(`💭 *Thinking: ${preview}*`);
|
|
92
|
+
}
|
|
93
|
+
else if (block.type === 'server_tool_use' && block.name) {
|
|
94
|
+
// Server-managed tools like web search
|
|
95
|
+
parts.push(`🌐 **${block.name}** ${block.input ? JSON.stringify(block.input).substring(0, 50) : ''}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return parts.length > 0 ? parts.join('\n') : null;
|
|
99
|
+
}
|
|
100
|
+
case 'tool_use': {
|
|
101
|
+
const tool = e.tool_use;
|
|
102
|
+
// Track tool start time for elapsed display
|
|
103
|
+
if (tool.id) {
|
|
104
|
+
session.activeToolStarts.set(tool.id, Date.now());
|
|
105
|
+
}
|
|
106
|
+
return sharedFormatToolUse(tool.name, tool.input || {}, session.platform.getFormatter(), { detailed: true }) || null;
|
|
107
|
+
}
|
|
108
|
+
case 'tool_result': {
|
|
109
|
+
const result = e.tool_result;
|
|
110
|
+
// Calculate elapsed time
|
|
111
|
+
let elapsed = '';
|
|
112
|
+
if (result.tool_use_id) {
|
|
113
|
+
const startTime = session.activeToolStarts.get(result.tool_use_id);
|
|
114
|
+
if (startTime) {
|
|
115
|
+
const secs = Math.round((Date.now() - startTime) / 1000);
|
|
116
|
+
if (secs >= 3) {
|
|
117
|
+
// Only show if >= 3 seconds
|
|
118
|
+
elapsed = ` (${secs}s)`;
|
|
119
|
+
}
|
|
120
|
+
session.activeToolStarts.delete(result.tool_use_id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (result.is_error)
|
|
124
|
+
return ` ↳ ❌ Error${elapsed}`;
|
|
125
|
+
if (elapsed)
|
|
126
|
+
return ` ↳ ✓${elapsed}`;
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
case 'result': {
|
|
130
|
+
// Response complete - stop typing and start new post for next message
|
|
131
|
+
ctx.stopTyping(session);
|
|
132
|
+
ctx.flush(session);
|
|
133
|
+
session.currentPostId = null;
|
|
134
|
+
session.pendingContent = '';
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
case 'system':
|
|
138
|
+
if (e.subtype === 'error')
|
|
139
|
+
return `❌ ${e.error}`;
|
|
140
|
+
return null;
|
|
141
|
+
case 'user': {
|
|
142
|
+
// Handle local command output (e.g., /context, /cost responses)
|
|
143
|
+
const msg = e.message;
|
|
144
|
+
if (typeof msg?.content === 'string') {
|
|
145
|
+
// Extract content from <local-command-stdout> tags
|
|
146
|
+
const match = msg.content.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
147
|
+
if (match) {
|
|
148
|
+
return match[1].trim();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
default:
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Plan mode handling
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Handle ExitPlanMode tool use - post approval prompt.
|
|
162
|
+
*/
|
|
163
|
+
async function handleExitPlanMode(session, toolUseId, ctx) {
|
|
164
|
+
// If already approved in this session, send empty tool result to acknowledge
|
|
165
|
+
if (session.planApproved) {
|
|
166
|
+
if (ctx.debug)
|
|
167
|
+
console.log(' ↪ Plan already approved, sending acknowledgment');
|
|
168
|
+
if (session.claude.isRunning()) {
|
|
169
|
+
session.claude.sendToolResult(toolUseId, 'Plan already approved. Proceeding.');
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// If we already have a pending approval, don't post another one
|
|
174
|
+
if (session.pendingApproval && session.pendingApproval.type === 'plan') {
|
|
175
|
+
if (ctx.debug)
|
|
176
|
+
console.log(' ↪ Plan approval already pending, waiting');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Flush any pending content first
|
|
180
|
+
await ctx.flush(session);
|
|
181
|
+
session.currentPostId = null;
|
|
182
|
+
session.pendingContent = '';
|
|
183
|
+
// Post approval message with reactions
|
|
184
|
+
const message = `✅ **Plan ready for approval**\n\n` +
|
|
185
|
+
`👍 Approve and start building\n` +
|
|
186
|
+
`👎 Request changes\n\n` +
|
|
187
|
+
`*React to respond*`;
|
|
188
|
+
const post = await session.platform.createInteractivePost(message, [APPROVAL_EMOJIS[0], DENIAL_EMOJIS[0]], session.threadId);
|
|
189
|
+
// Register post for reaction routing
|
|
190
|
+
ctx.registerPost(post.id, session.threadId);
|
|
191
|
+
// Track this for reaction handling - include toolUseId for proper response
|
|
192
|
+
session.pendingApproval = { postId: post.id, type: 'plan', toolUseId };
|
|
193
|
+
// Stop typing while waiting
|
|
194
|
+
ctx.stopTyping(session);
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Task/Todo handling
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
/**
|
|
200
|
+
* Handle TodoWrite tool use - update task list display.
|
|
201
|
+
*/
|
|
202
|
+
async function handleTodoWrite(session, input) {
|
|
203
|
+
const todos = input.todos;
|
|
204
|
+
if (!todos || todos.length === 0) {
|
|
205
|
+
// Clear tasks display if empty
|
|
206
|
+
if (session.tasksPostId) {
|
|
207
|
+
try {
|
|
208
|
+
await session.platform.updatePost(session.tasksPostId, '📋 ~~Tasks~~ *(completed)*');
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
console.error(' ⚠️ Failed to update tasks:', err);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Count progress
|
|
217
|
+
const completed = todos.filter((t) => t.status === 'completed').length;
|
|
218
|
+
const total = todos.length;
|
|
219
|
+
const pct = Math.round((completed / total) * 100);
|
|
220
|
+
// Check if there's an in_progress task and track timing
|
|
221
|
+
const hasInProgress = todos.some((t) => t.status === 'in_progress');
|
|
222
|
+
if (hasInProgress && !session.inProgressTaskStart) {
|
|
223
|
+
session.inProgressTaskStart = Date.now();
|
|
224
|
+
}
|
|
225
|
+
else if (!hasInProgress) {
|
|
226
|
+
session.inProgressTaskStart = null;
|
|
227
|
+
}
|
|
228
|
+
// Format tasks nicely with progress header
|
|
229
|
+
let message = `📋 **Tasks** (${completed}/${total} · ${pct}%)\n\n`;
|
|
230
|
+
for (const todo of todos) {
|
|
231
|
+
let icon;
|
|
232
|
+
let text;
|
|
233
|
+
switch (todo.status) {
|
|
234
|
+
case 'completed':
|
|
235
|
+
icon = '✅';
|
|
236
|
+
text = `~~${todo.content}~~`;
|
|
237
|
+
break;
|
|
238
|
+
case 'in_progress': {
|
|
239
|
+
icon = '🔄';
|
|
240
|
+
// Add elapsed time if we have a start time
|
|
241
|
+
let elapsed = '';
|
|
242
|
+
if (session.inProgressTaskStart) {
|
|
243
|
+
const secs = Math.round((Date.now() - session.inProgressTaskStart) / 1000);
|
|
244
|
+
if (secs >= 5) {
|
|
245
|
+
// Only show if >= 5 seconds
|
|
246
|
+
elapsed = ` (${secs}s)`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
text = `**${todo.activeForm}**${elapsed}`;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
default:
|
|
253
|
+
// pending
|
|
254
|
+
icon = '○';
|
|
255
|
+
text = todo.content;
|
|
256
|
+
}
|
|
257
|
+
message += `${icon} ${text}\n`;
|
|
258
|
+
}
|
|
259
|
+
// Update or create tasks post
|
|
260
|
+
try {
|
|
261
|
+
if (session.tasksPostId) {
|
|
262
|
+
await session.platform.updatePost(session.tasksPostId, message);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const post = await session.platform.createPost(message, session.threadId);
|
|
266
|
+
session.tasksPostId = post.id;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
console.error(' ⚠️ Failed to update tasks:', err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Handle Task (subagent) start - post status message.
|
|
275
|
+
*/
|
|
276
|
+
async function handleTaskStart(session, toolUseId, input) {
|
|
277
|
+
const description = input.description || 'Working...';
|
|
278
|
+
const subagentType = input.subagent_type || 'general';
|
|
279
|
+
// Post subagent status
|
|
280
|
+
const message = `🤖 **Subagent** *(${subagentType})*\n` + `> ${description}\n` + `⏳ Running...`;
|
|
281
|
+
try {
|
|
282
|
+
const post = await session.platform.createPost(message, session.threadId);
|
|
283
|
+
session.activeSubagents.set(toolUseId, post.id);
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
console.error(' ⚠️ Failed to post subagent status:', err);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Handle Task (subagent) completion - update status message.
|
|
291
|
+
*/
|
|
292
|
+
async function handleTaskComplete(session, toolUseId, postId) {
|
|
293
|
+
try {
|
|
294
|
+
await session.platform.updatePost(postId, session.activeSubagents.has(toolUseId)
|
|
295
|
+
? `🤖 **Subagent** ✅ *completed*`
|
|
296
|
+
: `🤖 **Subagent** ✅`);
|
|
297
|
+
session.activeSubagents.delete(toolUseId);
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
console.error(' ⚠️ Failed to update subagent completion:', err);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Question handling
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
/**
|
|
307
|
+
* Handle AskUserQuestion tool use - start interactive question flow.
|
|
308
|
+
*/
|
|
309
|
+
async function handleAskUserQuestion(session, toolUseId, input, ctx) {
|
|
310
|
+
// If we already have pending questions, don't start another set
|
|
311
|
+
if (session.pendingQuestionSet) {
|
|
312
|
+
if (ctx.debug)
|
|
313
|
+
console.log(' ↪ Questions already pending, waiting');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// Flush any pending content first
|
|
317
|
+
await ctx.flush(session);
|
|
318
|
+
session.currentPostId = null;
|
|
319
|
+
session.pendingContent = '';
|
|
320
|
+
const questions = input.questions;
|
|
321
|
+
if (!questions || questions.length === 0)
|
|
322
|
+
return;
|
|
323
|
+
// Create a new question set - we'll ask one at a time
|
|
324
|
+
session.pendingQuestionSet = {
|
|
325
|
+
toolUseId,
|
|
326
|
+
currentIndex: 0,
|
|
327
|
+
currentPostId: null,
|
|
328
|
+
questions: questions.map((q) => ({
|
|
329
|
+
header: q.header,
|
|
330
|
+
question: q.question,
|
|
331
|
+
options: q.options,
|
|
332
|
+
answer: null,
|
|
333
|
+
})),
|
|
334
|
+
};
|
|
335
|
+
// Post the first question
|
|
336
|
+
await postCurrentQuestion(session, ctx);
|
|
337
|
+
// Stop typing while waiting for answer
|
|
338
|
+
ctx.stopTyping(session);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Post the current question in the question set.
|
|
342
|
+
*/
|
|
343
|
+
export async function postCurrentQuestion(session, ctx) {
|
|
344
|
+
if (!session.pendingQuestionSet)
|
|
345
|
+
return;
|
|
346
|
+
const { currentIndex, questions } = session.pendingQuestionSet;
|
|
347
|
+
if (currentIndex >= questions.length)
|
|
348
|
+
return;
|
|
349
|
+
const q = questions[currentIndex];
|
|
350
|
+
const total = questions.length;
|
|
351
|
+
// Format the question message
|
|
352
|
+
let message = `❓ **Question** *(${currentIndex + 1}/${total})*\n`;
|
|
353
|
+
message += `**${q.header}:** ${q.question}\n\n`;
|
|
354
|
+
for (let i = 0; i < q.options.length && i < 4; i++) {
|
|
355
|
+
const emoji = ['1️⃣', '2️⃣', '3️⃣', '4️⃣'][i];
|
|
356
|
+
message += `${emoji} **${q.options[i].label}**`;
|
|
357
|
+
if (q.options[i].description) {
|
|
358
|
+
message += ` - ${q.options[i].description}`;
|
|
359
|
+
}
|
|
360
|
+
message += '\n';
|
|
361
|
+
}
|
|
362
|
+
// Post the question with reaction options
|
|
363
|
+
const reactionOptions = NUMBER_EMOJIS.slice(0, q.options.length);
|
|
364
|
+
const post = await session.platform.createInteractivePost(message, reactionOptions, session.threadId);
|
|
365
|
+
session.pendingQuestionSet.currentPostId = post.id;
|
|
366
|
+
// Register post for reaction routing
|
|
367
|
+
ctx.registerPost(post.id, session.threadId);
|
|
368
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session module public exports
|
|
3
|
+
*
|
|
4
|
+
* Provides SessionManager for managing multiple concurrent Claude Code sessions.
|
|
5
|
+
*/
|
|
6
|
+
export { SessionManager } from './manager.js';
|
|
7
|
+
export type { Session, PendingApproval, PendingQuestionSet, PendingMessageApproval, } from './types.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session lifecycle management module
|
|
3
|
+
*
|
|
4
|
+
* Handles session start, resume, exit, cleanup, and shutdown.
|
|
5
|
+
*/
|
|
6
|
+
import type { Session } from './types.js';
|
|
7
|
+
import type { PlatformClient, PlatformFile } from '../platform/index.js';
|
|
8
|
+
import type { ClaudeEvent, ContentBlock } from '../claude/cli.js';
|
|
9
|
+
import type { PersistedSession, SessionStore } from '../persistence/session-store.js';
|
|
10
|
+
export interface LifecycleContext {
|
|
11
|
+
workingDir: string;
|
|
12
|
+
skipPermissions: boolean;
|
|
13
|
+
chromeEnabled: boolean;
|
|
14
|
+
debug: boolean;
|
|
15
|
+
maxSessions: number;
|
|
16
|
+
sessions: Map<string, Session>;
|
|
17
|
+
postIndex: Map<string, string>;
|
|
18
|
+
platforms: Map<string, PlatformClient>;
|
|
19
|
+
sessionStore: SessionStore;
|
|
20
|
+
isShuttingDown: boolean;
|
|
21
|
+
getSessionId: (platformId: string, threadId: string) => string;
|
|
22
|
+
findSessionByThreadId: (threadId: string) => Session | undefined;
|
|
23
|
+
handleEvent: (sessionId: string, event: ClaudeEvent) => void;
|
|
24
|
+
handleExit: (sessionId: string, code: number) => Promise<void>;
|
|
25
|
+
registerPost: (postId: string, threadId: string) => void;
|
|
26
|
+
startTyping: (session: Session) => void;
|
|
27
|
+
stopTyping: (session: Session) => void;
|
|
28
|
+
flush: (session: Session) => Promise<void>;
|
|
29
|
+
persistSession: (session: Session) => void;
|
|
30
|
+
unpersistSession: (sessionId: string) => void;
|
|
31
|
+
updateSessionHeader: (session: Session) => Promise<void>;
|
|
32
|
+
shouldPromptForWorktree: (session: Session) => Promise<string | null>;
|
|
33
|
+
postWorktreePrompt: (session: Session, reason: string) => Promise<void>;
|
|
34
|
+
buildMessageContent: (text: string, platform: PlatformClient, files?: PlatformFile[]) => Promise<string | ContentBlock[]>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a new session for a thread.
|
|
38
|
+
*/
|
|
39
|
+
export declare function startSession(options: {
|
|
40
|
+
prompt: string;
|
|
41
|
+
files?: PlatformFile[];
|
|
42
|
+
}, username: string, replyToPostId: string | undefined, platformId: string, ctx: LifecycleContext): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Resume a session from persisted state.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resumeSession(state: PersistedSession, ctx: LifecycleContext): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Send a follow-up message to an existing session.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sendFollowUp(session: Session, message: string, files: PlatformFile[] | undefined, ctx: LifecycleContext): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Resume a paused session and send a message to it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resumePausedSession(threadId: string, message: string, files: PlatformFile[] | undefined, ctx: LifecycleContext): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Handle Claude CLI exit event.
|
|
57
|
+
*/
|
|
58
|
+
export declare function handleExit(sessionId: string, code: number, ctx: LifecycleContext): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Kill a specific session.
|
|
61
|
+
*/
|
|
62
|
+
export declare function killSession(session: Session, unpersist: boolean, ctx: LifecycleContext): void;
|
|
63
|
+
/**
|
|
64
|
+
* Kill all active sessions.
|
|
65
|
+
*/
|
|
66
|
+
export declare function killAllSessions(ctx: LifecycleContext): void;
|
|
67
|
+
/**
|
|
68
|
+
* Clean up idle sessions that have timed out.
|
|
69
|
+
*/
|
|
70
|
+
export declare function cleanupIdleSessions(timeoutMs: number, warningMs: number, ctx: LifecycleContext): void;
|