@teamvibe/poller 0.1.28 → 0.1.30

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.
@@ -11,3 +11,4 @@ export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: S
11
11
  }>;
12
12
  export declare function getActiveProcessCount(): number;
13
13
  export declare function isAtCapacity(): boolean;
14
+ export declare function killAllActiveProcesses(): Promise<void>;
@@ -243,8 +243,11 @@ async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = t
243
243
  stderr += chunk;
244
244
  sessionLog.claude('stderr', chunk.trimEnd());
245
245
  });
246
+ let timedOut = false;
247
+ const timeoutMinutes = Math.round(config.CLAUDE_TIMEOUT_MS / 60000);
246
248
  const timeout = setTimeout(() => {
247
- logger.error(`Claude Code process timed out after ${config.CLAUDE_TIMEOUT_MS}ms`);
249
+ timedOut = true;
250
+ logger.error(`Claude Code process timed out after ${timeoutMinutes} minutes`);
248
251
  proc.kill('SIGTERM');
249
252
  setTimeout(() => proc.kill('SIGKILL'), 5000);
250
253
  }, config.CLAUDE_TIMEOUT_MS);
@@ -256,7 +259,10 @@ async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = t
256
259
  output: stdout,
257
260
  exitCode: code,
258
261
  };
259
- if (code !== 0) {
262
+ if (timedOut) {
263
+ result.error = `Session timed out after ${timeoutMinutes} minutes. The task was still running when the time limit was reached. Consider breaking complex tasks into smaller steps.`;
264
+ }
265
+ else if (code !== 0) {
260
266
  result.error = stderr || `Process exited with code ${code}`;
261
267
  }
262
268
  resolve(result);
@@ -298,3 +304,26 @@ export function getActiveProcessCount() {
298
304
  export function isAtCapacity() {
299
305
  return activeProcesses.size >= config.MAX_CONCURRENT_SESSIONS;
300
306
  }
307
+ export function killAllActiveProcesses() {
308
+ if (activeProcesses.size === 0)
309
+ return Promise.resolve();
310
+ logger.info(`Killing ${activeProcesses.size} active Claude process(es)...`);
311
+ const killPromises = Array.from(activeProcesses.entries()).map(([id, proc]) => new Promise((resolve) => {
312
+ if (proc.exitCode !== null) {
313
+ resolve();
314
+ return;
315
+ }
316
+ const forceKillTimer = setTimeout(() => {
317
+ logger.warn(`Process ${id} did not exit after SIGTERM, sending SIGKILL`);
318
+ proc.kill('SIGKILL');
319
+ }, 5000);
320
+ proc.once('close', () => {
321
+ clearTimeout(forceKillTimer);
322
+ resolve();
323
+ });
324
+ proc.kill('SIGTERM');
325
+ }));
326
+ return Promise.all(killPromises).then(() => {
327
+ logger.info('All active processes terminated');
328
+ });
329
+ }
package/dist/config.js CHANGED
@@ -19,8 +19,8 @@ const configSchema = z.object({
19
19
  POLL_WAIT_TIME_SECONDS: z.coerce.number().default(20),
20
20
  VISIBILITY_TIMEOUT_SECONDS: z.coerce.number().default(60),
21
21
  HEARTBEAT_INTERVAL_MS: z.coerce.number().default(40000), // 40 seconds
22
- CLAUDE_TIMEOUT_MS: z.coerce.number().default(1800000), // 30 minutes
23
- STALE_LOCK_TIMEOUT_MS: z.coerce.number().default(2100000), // 35 minutes
22
+ CLAUDE_TIMEOUT_MS: z.coerce.number().default(3600000), // 60 minutes
23
+ STALE_LOCK_TIMEOUT_MS: z.coerce.number().default(3900000), // 65 minutes
24
24
  // Paths
25
25
  TEAMVIBE_DATA_DIR: z.string().default(DEFAULT_DATA_DIR),
26
26
  BRAINS_PATH: z.string().default(''),
package/dist/poller.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { config } from './config.js';
2
2
  import { logger } from './logger.js';
3
3
  import { pollMessages, deleteMessage, extendVisibility, } from './sqs-poller.js';
4
- import { spawnClaudeCode, isAtCapacity, getActiveProcessCount } from './claude-spawner.js';
4
+ import { spawnClaudeCode, isAtCapacity, getActiveProcessCount, killAllActiveProcesses } from './claude-spawner.js';
5
5
  import { sendSlackError, addReaction, getUserInfo, startTypingIndicator } from './slack-client.js';
6
6
  import { acquireSessionLock, releaseSessionLock, updateSessionId } from './session-store.js';
7
7
  import { getBrainPath, ensureDirectories, ensureBaseBrain, pushBrainChanges } from './brain-manager.js';
@@ -151,8 +151,10 @@ async function processMessage(received) {
151
151
  else {
152
152
  sessionLog.error(`Claude Code failed: ${result.error}`);
153
153
  if (hasSlackContext) {
154
- await sendSlackError(queueMessage, result.error || `Process exited with code ${result.exitCode}`);
155
- await addReaction(queueMessage, 'x');
154
+ const errorMessage = result.error || `Process exited with code ${result.exitCode}`;
155
+ const isTimeout = errorMessage.includes('timed out');
156
+ await sendSlackError(queueMessage, errorMessage);
157
+ await addReaction(queueMessage, isTimeout ? 'hourglass' : 'x');
156
158
  }
157
159
  if (lockToken) {
158
160
  await releaseSessionLock(threadId, lockToken, 'idle');
@@ -191,7 +193,7 @@ async function processMessage(received) {
191
193
  }
192
194
  async function pollLoop() {
193
195
  logger.info('Poll loop started');
194
- while (true) {
196
+ while (!shuttingDown) {
195
197
  try {
196
198
  if (isAtCapacity()) {
197
199
  logger.debug(`At capacity (${getActiveProcessCount()}/${config.MAX_CONCURRENT_SESSIONS}), waiting...`);
@@ -229,6 +231,16 @@ async function shutdown(signal) {
229
231
  shuttingDown = true;
230
232
  logger.info(`${signal} received, shutting down gracefully...`);
231
233
  stopRefresh();
234
+ // Unblock all messages waiting on thread completion so they can exit
235
+ for (const [threadId, signals] of threadCompletionSignals.entries()) {
236
+ logger.info(`Unblocking ${signals.length} waiting message(s) on thread ${threadId}`);
237
+ signals.forEach((resolve) => resolve());
238
+ }
239
+ threadCompletionSignals.clear();
240
+ waitingCountByThread.clear();
241
+ // Kill all active Claude child processes
242
+ await killAllActiveProcesses();
243
+ // Wait for processingMessages to drain (with timeout)
232
244
  const shutdownTimeout = 30000;
233
245
  const startTime = Date.now();
234
246
  while (processingMessages.size > 0) {
@@ -60,10 +60,14 @@ export async function sendSlackError(msg, error) {
60
60
  return;
61
61
  const { channel, thread_ts } = msg.response_context.slack;
62
62
  const slack = getSlackClient(msg.teamvibe.botToken);
63
+ const isTimeout = error.includes('timed out');
64
+ const text = isTimeout
65
+ ? `:warning: ${error}`
66
+ : `Error processing your request:\n\`\`\`\n${error}\n\`\`\``;
63
67
  await slack.chat.postMessage({
64
68
  channel,
65
69
  thread_ts,
66
- text: `Error processing your request:\n\`\`\`\n${error}\n\`\`\``,
70
+ text,
67
71
  });
68
72
  }
69
73
  export async function addReaction(msg, emoji) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamvibe/poller",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {