@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 CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function setLogContext(ctx: Record<string, string>): void;
1
2
  export declare class SessionLogger {
2
3
  private sessionId;
3
4
  private logFile;
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
- console.log(`[${ts}] [${this.sessionId}] ${message}`);
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
- console.error(`[${ts}] [${this.sessionId}] ${message}`);
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
- console.warn(`[${ts}] [${this.sessionId}] ${message}`);
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
- console.log(`[${ts}] [INFO] ${message}`, ...args);
87
- writeToFile('INFO', message, ts, ...args);
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
- console.error(`[${ts}] [ERROR] ${message}`, ...args);
92
- writeToFile('ERROR', message, ts, ...args);
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
- console.warn(`[${ts}] [WARN] ${message}`, ...args);
97
- writeToFile('WARN', message, ts, ...args);
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, ts, ...args);
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('TeamVibe Poller starting...');
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamvibe/poller",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {