@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.
- package/dist/claude-spawner.d.ts +1 -0
- package/dist/claude-spawner.js +31 -2
- package/dist/config.js +2 -2
- package/dist/poller.js +16 -4
- package/dist/slack-client.js +5 -1
- package/package.json +1 -1
package/dist/claude-spawner.d.ts
CHANGED
package/dist/claude-spawner.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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(
|
|
23
|
-
STALE_LOCK_TIMEOUT_MS: z.coerce.number().default(
|
|
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
|
-
|
|
155
|
-
|
|
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 (
|
|
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) {
|
package/dist/slack-client.js
CHANGED
|
@@ -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
|
|
70
|
+
text,
|
|
67
71
|
});
|
|
68
72
|
}
|
|
69
73
|
export async function addReaction(msg, emoji) {
|