@teamvibe/poller 0.5.2 → 0.5.3
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/logger.d.ts +1 -0
- package/dist/logger.js +32 -13
- package/dist/poller.js +35 -5
- package/package.json +1 -1
package/dist/logger.d.ts
CHANGED
package/dist/logger.js
CHANGED
|
@@ -15,6 +15,17 @@ function formatDate(date) {
|
|
|
15
15
|
return date.toISOString();
|
|
16
16
|
}
|
|
17
17
|
const now = () => formatDate(new Date());
|
|
18
|
+
// Optional global context (e.g. pollerId) prepended to every log line so an
|
|
19
|
+
// operator watching Railway stdout can filter by poller without a session id.
|
|
20
|
+
// Set once after auth resolves; see setLogContext call in poller startup.
|
|
21
|
+
let logContext = {};
|
|
22
|
+
export function setLogContext(ctx) {
|
|
23
|
+
logContext = { ...logContext, ...ctx };
|
|
24
|
+
}
|
|
25
|
+
function contextPrefix() {
|
|
26
|
+
const entries = Object.entries(logContext);
|
|
27
|
+
return entries.length ? entries.map(([k, v]) => `${k}=${v}`).join(' ') + ' ' : '';
|
|
28
|
+
}
|
|
18
29
|
function writeToFile(level, message, timestamp, ...args) {
|
|
19
30
|
const formattedArgs = args
|
|
20
31
|
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
|
@@ -46,30 +57,34 @@ export class SessionLogger {
|
|
|
46
57
|
}
|
|
47
58
|
info(message) {
|
|
48
59
|
const ts = now();
|
|
49
|
-
|
|
60
|
+
const cp = contextPrefix();
|
|
61
|
+
console.log(`[${ts}] [${this.sessionId}] ${cp}${message}`);
|
|
50
62
|
this.write('INFO', message, ts);
|
|
51
63
|
writeToFile('INFO', `[${this.sessionId}] ${message}`, ts);
|
|
52
64
|
}
|
|
53
65
|
error(message) {
|
|
54
66
|
const ts = now();
|
|
55
|
-
|
|
67
|
+
const cp = contextPrefix();
|
|
68
|
+
console.error(`[${ts}] [${this.sessionId}] ${cp}${message}`);
|
|
56
69
|
this.write('ERROR', message, ts);
|
|
57
70
|
writeToFile('ERROR', `[${this.sessionId}] ${message}`, ts);
|
|
58
71
|
}
|
|
59
72
|
warn(message) {
|
|
60
73
|
const ts = now();
|
|
61
|
-
|
|
74
|
+
const cp = contextPrefix();
|
|
75
|
+
console.warn(`[${ts}] [${this.sessionId}] ${cp}${message}`);
|
|
62
76
|
this.write('WARN', message, ts);
|
|
63
77
|
writeToFile('WARN', `[${this.sessionId}] ${message}`, ts);
|
|
64
78
|
}
|
|
65
79
|
claude(stream, message) {
|
|
66
80
|
const ts = now();
|
|
81
|
+
const cp = contextPrefix();
|
|
67
82
|
const prefix = `[Claude ${stream}]`;
|
|
68
83
|
if (stream === 'stderr') {
|
|
69
|
-
console.error(`[${ts}] [${this.sessionId}] ${prefix}`, message);
|
|
84
|
+
console.error(`[${ts}] [${this.sessionId}] ${cp}${prefix}`, message);
|
|
70
85
|
}
|
|
71
86
|
else {
|
|
72
|
-
console.log(`[${ts}] [${this.sessionId}] ${prefix}`, message);
|
|
87
|
+
console.log(`[${ts}] [${this.sessionId}] ${cp}${prefix}`, message);
|
|
73
88
|
}
|
|
74
89
|
this.write(`CLAUDE_${stream.toUpperCase()}`, message, ts);
|
|
75
90
|
}
|
|
@@ -83,25 +98,29 @@ export class SessionLogger {
|
|
|
83
98
|
export const logger = {
|
|
84
99
|
info(message, ...args) {
|
|
85
100
|
const ts = now();
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
const cp = contextPrefix();
|
|
102
|
+
console.log(`[${ts}] [INFO] ${cp}${message}`, ...args);
|
|
103
|
+
writeToFile('INFO', `${cp}${message}`, ts, ...args);
|
|
88
104
|
},
|
|
89
105
|
error(message, ...args) {
|
|
90
106
|
const ts = now();
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
const cp = contextPrefix();
|
|
108
|
+
console.error(`[${ts}] [ERROR] ${cp}${message}`, ...args);
|
|
109
|
+
writeToFile('ERROR', `${cp}${message}`, ts, ...args);
|
|
93
110
|
},
|
|
94
111
|
warn(message, ...args) {
|
|
95
112
|
const ts = now();
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
const cp = contextPrefix();
|
|
114
|
+
console.warn(`[${ts}] [WARN] ${cp}${message}`, ...args);
|
|
115
|
+
writeToFile('WARN', `${cp}${message}`, ts, ...args);
|
|
98
116
|
},
|
|
99
117
|
debug(message, ...args) {
|
|
100
118
|
const ts = now();
|
|
119
|
+
const cp = contextPrefix();
|
|
101
120
|
if (process.env['DEBUG']) {
|
|
102
|
-
console.debug(`[${ts}] [DEBUG] ${message}`, ...args);
|
|
121
|
+
console.debug(`[${ts}] [DEBUG] ${cp}${message}`, ...args);
|
|
103
122
|
}
|
|
104
|
-
writeToFile('DEBUG', message
|
|
123
|
+
writeToFile('DEBUG', `${cp}${message}`, ts, ...args);
|
|
105
124
|
},
|
|
106
125
|
createSession(sessionId) {
|
|
107
126
|
return new SessionLogger(sessionId);
|
package/dist/poller.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { config } from './config.js';
|
|
2
|
-
import { logger } from './logger.js';
|
|
1
|
+
import { config, POLLER_VERSION } from './config.js';
|
|
2
|
+
import { logger, setLogContext } from './logger.js';
|
|
3
3
|
import { pollMessages, deleteMessage, } from './sqs-poller.js';
|
|
4
4
|
import { spawnClaudeCode, isAtCapacity, getActiveProcessCount, killAllActiveProcesses, killZombieForThread } from './claude-spawner.js';
|
|
5
5
|
import { sendSlackError, sendSlackMessage, addReaction, getUserInfo, startTypingIndicator } from './slack-client.js';
|
|
@@ -8,9 +8,9 @@ import { acquireSessionLock, releaseSessionLock, updateSessionId } from './sessi
|
|
|
8
8
|
import { getBrainPath, ensureDirectories, ensureBaseBrain, pushBrainChanges } from './brain-manager.js';
|
|
9
9
|
import { cleanupOrphanWorktreesForAllBrains, isWorktreeIsolationEnabled } from './worktree-manager.js';
|
|
10
10
|
import { postMaintenanceReports } from './maintenance-reporter.js';
|
|
11
|
-
import { initAuth, stopRefresh } from './auth-provider.js';
|
|
11
|
+
import { initAuth, stopRefresh, getPollerId } from './auth-provider.js';
|
|
12
12
|
import { registerBrain, startMaintenanceLoop, stopMaintenanceLoop } from './maintenance-cron.js';
|
|
13
|
-
import { startEventQueue, stopEventQueue, flush as flushEventQueue, emitEvent } from './event-queue.js';
|
|
13
|
+
import { startEventQueue, stopEventQueue, flush as flushEventQueue, emitEvent, emitError } from './event-queue.js';
|
|
14
14
|
import { startMetricsCron, stopMetricsCron } from './metrics/scheduler.js';
|
|
15
15
|
import { writeMessage, pickNextMessage, markProcessed, markFailed, hasUnprocessed } from './inbox-manager.js';
|
|
16
16
|
import { Cron } from 'croner';
|
|
@@ -533,14 +533,44 @@ async function shutdown(signal, exitCode = 0) {
|
|
|
533
533
|
process.exit(exitCode);
|
|
534
534
|
}
|
|
535
535
|
export async function startPoller() {
|
|
536
|
-
logger.info(
|
|
536
|
+
logger.info(`TeamVibe Poller v${POLLER_VERSION} starting...`);
|
|
537
537
|
logger.info(` Max concurrent: ${config.MAX_CONCURRENT_SESSIONS}`);
|
|
538
538
|
logger.info(` Brains path: ${config.BRAINS_PATH}`);
|
|
539
539
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
540
540
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
541
|
+
// Last-resort handlers so a hard crash produces one tagged ERROR line (+ a
|
|
542
|
+
// best-effort event flush) instead of an untagged default Node stack trace —
|
|
543
|
+
// these are exactly the failures that never reach /events on their own.
|
|
544
|
+
process.on('uncaughtException', (err) => {
|
|
545
|
+
const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
546
|
+
logger.error(`Uncaught exception, shutting down: ${detail}`);
|
|
547
|
+
emitError('session', `Uncaught exception: ${err instanceof Error ? err.message : String(err)}`, {
|
|
548
|
+
severity: 'error',
|
|
549
|
+
});
|
|
550
|
+
// The only invariant that matters in an undefined process state is that we
|
|
551
|
+
// actually exit. A stuck flush (e.g. the hung platform connection we want to
|
|
552
|
+
// report) must never block termination, so time-bound the best-effort flush.
|
|
553
|
+
const boundedFlush = Promise.race([
|
|
554
|
+
flushEventQueue(),
|
|
555
|
+
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
556
|
+
]);
|
|
557
|
+
void boundedFlush.finally(() => process.exit(1));
|
|
558
|
+
});
|
|
559
|
+
process.on('unhandledRejection', (reason) => {
|
|
560
|
+
const detail = reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);
|
|
561
|
+
logger.error(`Unhandled promise rejection: ${detail}`);
|
|
562
|
+
emitError('session', `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`, {
|
|
563
|
+
severity: 'error',
|
|
564
|
+
});
|
|
565
|
+
});
|
|
541
566
|
// Token-based auth: fetch credentials before starting
|
|
542
567
|
if (config.TEAMVIBE_API_URL && config.TEAMVIBE_POLLER_TOKEN) {
|
|
543
568
|
await initAuth(config.TEAMVIBE_API_URL, config.TEAMVIBE_POLLER_TOKEN);
|
|
569
|
+
const pollerId = getPollerId();
|
|
570
|
+
if (pollerId) {
|
|
571
|
+
setLogContext({ pollerId });
|
|
572
|
+
logger.info(` Poller ID: ${pollerId}`);
|
|
573
|
+
}
|
|
544
574
|
}
|
|
545
575
|
else {
|
|
546
576
|
logger.info(` Queue: ${config.SQS_QUEUE_URL}`);
|