@teamvibe/poller 0.1.30 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-spawner.d.ts +2 -1
- package/dist/claude-spawner.js +58 -5
- package/dist/poller.js +4 -4
- package/dist/slack-client.d.ts +7 -1
- package/dist/slack-client.js +14 -6
- package/package.json +1 -1
package/dist/claude-spawner.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { TeamVibeQueueMessage } from './types.js';
|
|
2
2
|
import { type SessionLogger } from './logger.js';
|
|
3
|
+
import type { TypingIndicator } from './slack-client.js';
|
|
3
4
|
export interface SpawnResult {
|
|
4
5
|
success: boolean;
|
|
5
6
|
output: string;
|
|
6
7
|
exitCode: number | null;
|
|
7
8
|
error?: string;
|
|
8
9
|
}
|
|
9
|
-
export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, cwd: string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void): Promise<SpawnResult & {
|
|
10
|
+
export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, cwd: string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void, typingIndicator?: TypingIndicator): Promise<SpawnResult & {
|
|
10
11
|
newSessionId?: string;
|
|
11
12
|
}>;
|
|
12
13
|
export declare function getActiveProcessCount(): number;
|
package/dist/claude-spawner.js
CHANGED
|
@@ -4,6 +4,45 @@ import { WebClient } from '@slack/web-api';
|
|
|
4
4
|
import { config } from './config.js';
|
|
5
5
|
import { logger } from './logger.js';
|
|
6
6
|
import { getBaseBrainPath } from './brain-manager.js';
|
|
7
|
+
/**
|
|
8
|
+
* Maps tool names (or prefixes) to human-readable status messages.
|
|
9
|
+
* More specific patterns are checked first.
|
|
10
|
+
*/
|
|
11
|
+
const TOOL_STATUS_MAP = [
|
|
12
|
+
// Slack MCP tools
|
|
13
|
+
{ pattern: 'mcp__slack__search_messages', status: 'Searching Slack messages...' },
|
|
14
|
+
{ pattern: 'mcp__slack__read_thread', status: 'Reading thread...' },
|
|
15
|
+
{ pattern: 'mcp__slack__send_message', status: 'Sending message...' },
|
|
16
|
+
{ pattern: 'mcp__slack__set_status', status: '' }, // Don't override Claude's own status
|
|
17
|
+
{ pattern: 'mcp__slack__upload_snippet', status: 'Uploading snippet...' },
|
|
18
|
+
{ pattern: /^mcp__slack__/, status: 'Working with Slack...' },
|
|
19
|
+
// Web tools
|
|
20
|
+
{ pattern: 'WebSearch', status: 'Searching the web...' },
|
|
21
|
+
{ pattern: 'WebFetch', status: 'Reading a webpage...' },
|
|
22
|
+
// File/code tools
|
|
23
|
+
{ pattern: 'Read', status: 'Reading files...' },
|
|
24
|
+
{ pattern: 'Grep', status: 'Searching code...' },
|
|
25
|
+
{ pattern: 'Glob', status: 'Finding files...' },
|
|
26
|
+
{ pattern: 'Edit', status: 'Editing code...' },
|
|
27
|
+
{ pattern: 'Write', status: 'Writing code...' },
|
|
28
|
+
{ pattern: 'Bash', status: 'Running a command...' },
|
|
29
|
+
{ pattern: 'Agent', status: 'Delegating to a sub-agent...' },
|
|
30
|
+
// Generic MCP fallback
|
|
31
|
+
{ pattern: /^mcp__/, status: 'Using an integration...' },
|
|
32
|
+
];
|
|
33
|
+
function getStatusForTool(toolName) {
|
|
34
|
+
for (const { pattern, status } of TOOL_STATUS_MAP) {
|
|
35
|
+
if (typeof pattern === 'string') {
|
|
36
|
+
if (toolName === pattern)
|
|
37
|
+
return status;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (pattern.test(toolName))
|
|
41
|
+
return status;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
7
46
|
// Per-token Slack client cache for thread context
|
|
8
47
|
const slackClients = new Map();
|
|
9
48
|
function getSlackClient(botToken) {
|
|
@@ -126,7 +165,7 @@ function formatStreamEvent(event) {
|
|
|
126
165
|
return null;
|
|
127
166
|
}
|
|
128
167
|
}
|
|
129
|
-
async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = true, lastMessageTs, onMessageSent) {
|
|
168
|
+
async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = true, lastMessageTs, onMessageSent, typingIndicator) {
|
|
130
169
|
const slackContext = msg.response_context.slack;
|
|
131
170
|
const isCronMessage = msg.source === 'cron';
|
|
132
171
|
if (!slackContext && !isCronMessage) {
|
|
@@ -231,6 +270,20 @@ async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = t
|
|
|
231
270
|
event.name === 'mcp__slack__send_message') {
|
|
232
271
|
onMessageSent();
|
|
233
272
|
}
|
|
273
|
+
// Auto-update typing indicator status based on tool usage
|
|
274
|
+
if (typingIndicator) {
|
|
275
|
+
if (event.type === 'tool_use' && event.name) {
|
|
276
|
+
const status = getStatusForTool(event.name);
|
|
277
|
+
if (status !== null && status !== '') {
|
|
278
|
+
typingIndicator.updateStatus(status);
|
|
279
|
+
}
|
|
280
|
+
// When status is '' (e.g. set_status tool), don't override — Claude is managing it
|
|
281
|
+
}
|
|
282
|
+
else if (event.type === 'assistant') {
|
|
283
|
+
// Reset to "Thinking..." when Claude is reasoning between tool calls
|
|
284
|
+
typingIndicator.updateStatus('Thinking...');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
234
287
|
}
|
|
235
288
|
catch {
|
|
236
289
|
const truncated = line.length > 500 ? line.slice(0, 500) + '...' : line;
|
|
@@ -279,20 +332,20 @@ async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = t
|
|
|
279
332
|
});
|
|
280
333
|
});
|
|
281
334
|
}
|
|
282
|
-
export async function spawnClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = true, lastMessageTs, onMessageSent) {
|
|
283
|
-
const result = await runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage, lastMessageTs, onMessageSent);
|
|
335
|
+
export async function spawnClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = true, lastMessageTs, onMessageSent, typingIndicator) {
|
|
336
|
+
const result = await runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage, lastMessageTs, onMessageSent, typingIndicator);
|
|
284
337
|
// If --resume failed, retry as a fresh session (session files may have been lost on container restart)
|
|
285
338
|
let retryResult = result;
|
|
286
339
|
if (!result.success && sessionId && !isFirstMessage) {
|
|
287
340
|
sessionLog.info('Resume failed, retrying as fresh session (session files may have been lost)');
|
|
288
|
-
retryResult = await runClaudeCode(msg, sessionLog, cwd, sessionId, true, lastMessageTs, onMessageSent);
|
|
341
|
+
retryResult = await runClaudeCode(msg, sessionLog, cwd, sessionId, true, lastMessageTs, onMessageSent, typingIndicator);
|
|
289
342
|
}
|
|
290
343
|
// If session ID is "already in use" (stale lock file), retry with a fresh session ID
|
|
291
344
|
if (!retryResult.success && sessionId && retryResult.error?.includes('already in use')) {
|
|
292
345
|
sessionLog.info('Session ID already in use (stale lock), retrying with fresh session ID');
|
|
293
346
|
const { randomUUID } = await import('crypto');
|
|
294
347
|
const freshId = randomUUID();
|
|
295
|
-
const freshResult = await runClaudeCode(msg, sessionLog, cwd, freshId, true, lastMessageTs, onMessageSent);
|
|
348
|
+
const freshResult = await runClaudeCode(msg, sessionLog, cwd, freshId, true, lastMessageTs, onMessageSent, typingIndicator);
|
|
296
349
|
return { ...freshResult, newSessionId: freshId };
|
|
297
350
|
}
|
|
298
351
|
return retryResult;
|
package/dist/poller.js
CHANGED
|
@@ -125,14 +125,14 @@ async function processMessage(received) {
|
|
|
125
125
|
processingMessages.add(messageId);
|
|
126
126
|
const heartbeat = startHeartbeat(receiptHandle, sessionLog);
|
|
127
127
|
// Start typing indicator
|
|
128
|
-
const
|
|
128
|
+
const typing = hasSlackContext
|
|
129
129
|
? startTypingIndicator(queueMessage)
|
|
130
130
|
: undefined;
|
|
131
131
|
try {
|
|
132
132
|
// Refresh base brain if cooldown has elapsed (5 min default)
|
|
133
133
|
await ensureBaseBrain();
|
|
134
|
-
const result = await spawnClaudeCode(queueMessage, sessionLog, kbPath, session.session_id || undefined, isFirstMessage, session.last_message_ts, () =>
|
|
135
|
-
|
|
134
|
+
const result = await spawnClaudeCode(queueMessage, sessionLog, kbPath, session.session_id || undefined, isFirstMessage, session.last_message_ts, () => typing?.stop(), typing);
|
|
135
|
+
typing?.stop();
|
|
136
136
|
// If Claude generated a new session ID (stale lock recovery), persist it
|
|
137
137
|
if (result.newSessionId && lockToken) {
|
|
138
138
|
await updateSessionId(threadId, lockToken, result.newSessionId);
|
|
@@ -164,7 +164,7 @@ async function processMessage(received) {
|
|
|
164
164
|
sessionLog.info('Message processed and deleted');
|
|
165
165
|
}
|
|
166
166
|
catch (error) {
|
|
167
|
-
|
|
167
|
+
typing?.stop();
|
|
168
168
|
sessionLog.error(`Error processing message: ${error instanceof Error ? error.message : error}`);
|
|
169
169
|
if (lockToken) {
|
|
170
170
|
try {
|
package/dist/slack-client.d.ts
CHANGED
|
@@ -7,5 +7,11 @@ export declare function sendSlackMessage(msg: TeamVibeQueueMessage, text: string
|
|
|
7
7
|
export declare function sendSlackError(msg: TeamVibeQueueMessage, error: string): Promise<void>;
|
|
8
8
|
export declare function addReaction(msg: TeamVibeQueueMessage, emoji: string): Promise<void>;
|
|
9
9
|
export declare function setThreadStatus(msg: TeamVibeQueueMessage, status: string): Promise<void>;
|
|
10
|
-
export
|
|
10
|
+
export interface TypingIndicator {
|
|
11
|
+
/** Update the displayed status text */
|
|
12
|
+
updateStatus: (newStatus: string) => void;
|
|
13
|
+
/** Stop the typing indicator and clear the status */
|
|
14
|
+
stop: () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function startTypingIndicator(msg: TeamVibeQueueMessage, statusText?: string): TypingIndicator;
|
|
11
17
|
export declare function removeReaction(msg: TeamVibeQueueMessage, emoji: string): Promise<void>;
|
package/dist/slack-client.js
CHANGED
|
@@ -103,13 +103,14 @@ export async function setThreadStatus(msg, status) {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
export function startTypingIndicator(msg, statusText = 'is thinking...') {
|
|
106
|
+
let currentStatus = statusText;
|
|
106
107
|
// Set initial status
|
|
107
|
-
setThreadStatus(msg,
|
|
108
|
+
setThreadStatus(msg, currentStatus);
|
|
108
109
|
// Keepalive every 3 seconds (Slack clears after ~2 min timeout)
|
|
109
110
|
let failures = 0;
|
|
110
111
|
const interval = setInterval(async () => {
|
|
111
112
|
try {
|
|
112
|
-
await setThreadStatus(msg,
|
|
113
|
+
await setThreadStatus(msg, currentStatus);
|
|
113
114
|
failures = 0;
|
|
114
115
|
}
|
|
115
116
|
catch {
|
|
@@ -119,10 +120,17 @@ export function startTypingIndicator(msg, statusText = 'is thinking...') {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
}, 3000);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
return {
|
|
124
|
+
updateStatus: (newStatus) => {
|
|
125
|
+
if (newStatus !== currentStatus) {
|
|
126
|
+
currentStatus = newStatus;
|
|
127
|
+
setThreadStatus(msg, currentStatus);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
stop: () => {
|
|
131
|
+
clearInterval(interval);
|
|
132
|
+
setThreadStatus(msg, '');
|
|
133
|
+
},
|
|
126
134
|
};
|
|
127
135
|
}
|
|
128
136
|
export async function removeReaction(msg, emoji) {
|