hobots 2.2.0 → 2.4.0

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/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { Breadcrumb, EventUser, EventType, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunProgress, RunAttachmentContentType } from './protocol.cjs';
2
- export { BreadcrumbLevel, Envelope, EnvelopeItem, ExceptionData, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.cjs';
1
+ import { Breadcrumb, EventUser, EventType, MachineSnapshot, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunProgress, RunAttachmentContentType, EnvironmentInput } from './protocol.cjs';
2
+ export { BreadcrumbLevel, Envelope, EnvelopeItem, ExceptionData, InstanceInfo, MachineProcess, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.cjs';
3
3
 
4
4
  /** Aceita tudo que o handler pode ter em mãos: bytes ou um caminho de arquivo (só Node). */
5
5
  type AttachInput = Uint8Array | ArrayBuffer | string;
@@ -47,15 +47,46 @@ interface Logger {
47
47
  error: (...args: unknown[]) => void;
48
48
  }
49
49
 
50
+ /**
51
+ * Coleta CPU/memória/disco da máquina e do processo — só em Node (no browser é no-op).
52
+ *
53
+ * Os módulos `node:*` entram por import dinâmico (o bundle é `platform: neutral`), e
54
+ * `sample()` é síncrono e barato: `os.cpus()` + `statfsSync` + `process.memoryUsage()`.
55
+ * Nunca lança — telemetria de recurso não pode derrubar o bot nem o capture de um erro.
56
+ */
57
+ declare class MachineSampler {
58
+ private readonly instanceId;
59
+ private readonly logger;
60
+ private os;
61
+ private fs;
62
+ private ready;
63
+ private prevCpu;
64
+ private prevProcessCpu;
65
+ private prevAt;
66
+ private lastCpuPercent;
67
+ private lastProcessCpuPercent;
68
+ constructor(instanceId: string, logger: Logger);
69
+ /** Carrega os módulos e tira a leitura-base de CPU (a primeira amostra ainda sai com cpu_percent null). */
70
+ start(): Promise<void>;
71
+ sample(): MachineSnapshot | undefined;
72
+ private cpuTotals;
73
+ /** Média desde a leitura anterior; leituras com menos de 1s de janela reutilizam a última média. */
74
+ private updateCpu;
75
+ private diskUsage;
76
+ }
77
+
50
78
  declare class Client {
51
79
  #private;
52
80
  readonly logger: Logger;
53
81
  /** Eco de lifecycle (Transaction.emit) e afins só imprimem no terminal com debug ligado. */
54
82
  readonly debug: boolean;
55
83
  private readonly transport;
84
+ private machine;
56
85
  constructor(options: InitOptions, config?: ResolvedConfig);
57
86
  get options(): InitOptions;
58
87
  get config(): ResolvedConfig;
88
+ /** Sampler de recursos da máquina — cada evento passa a levar `contexts.machine`. */
89
+ attachMachineSampler(sampler: MachineSampler | undefined): void;
59
90
  /** Serialização explícita nunca inclui o secret. */
60
91
  toJSON(): Record<string, unknown>;
61
92
  captureException(error: unknown, hint: CaptureHint | undefined, scope: Scope): string;
@@ -64,6 +95,12 @@ declare class Client {
64
95
  private prepareEvent;
65
96
  sendTransaction(payload: TransactionPayload): void;
66
97
  sendHeartbeat(payload: HeartbeatPayload): void;
98
+ /**
99
+ * Ambiente do item: o dele, senão o do init(), senão produção. Centralizado
100
+ * porque evento, transaction e heartbeat precisam responder igual — o servidor
101
+ * normaliza apelidos legados, então aqui basta repassar.
102
+ */
103
+ private resolveEnvironment;
67
104
  flush(timeoutMs?: number): Promise<boolean>;
68
105
  close(timeoutMs?: number): Promise<boolean>;
69
106
  }
@@ -131,7 +168,7 @@ declare class Transaction {
131
168
 
132
169
  /** Opções de tasks dentro de `init({ tasks: ... })`. Credencial (clientSecret), identidade (instanceId) e debug vêm do init. */
133
170
  interface TasksOptions {
134
- /** Intervalo entre polls sem trabalho. Default: 5_000 (mínimo 1_000) */
171
+ /** Intervalo entre polls sem trabalho. Default: 2_000 (mínimo 1_000) */
135
172
  pollIntervalMs?: number;
136
173
  /** Runs simultâneas (1..5). Default: 1 (sequencial) */
137
174
  concurrency?: number;
@@ -254,8 +291,16 @@ interface HeartbeatOptions {
254
291
  interface InitOptions {
255
292
  /** O Client Secret do agente (hb_…) — a mesma credencial para telemetria e tasks. Copie em Configurações → Agentes. */
256
293
  clientSecret: string;
257
- /** Default: 'production' */
258
- environment?: string;
294
+ /**
295
+ * Ambiente lógico onde este robô roda: 'development', 'testing', 'staging'
296
+ * (homologação) ou 'production'. Vai em todo evento e transaction E na
297
+ * identidade da instância — é por ele que as regras de alerta decidem se
298
+ * notificam. Default: 'production'.
299
+ *
300
+ * Um valor livre herdado continua valendo — o servidor normaliza apelidos
301
+ * como 'prod' e 'qa'.
302
+ */
303
+ environment?: EnvironmentInput;
259
304
  /** Versão do programa, ex.: 'robo-nfe@1.4.2' */
260
305
  release?: string;
261
306
  /** Identificador único e estável desta instância — a identidade do agente no painel (heartbeat e poll de tasks convergem nele) */
@@ -270,6 +315,12 @@ interface InitOptions {
270
315
  maxBreadcrumbs?: number;
271
316
  /** Registra handlers globais de erro (uncaughtException etc.). Default: true */
272
317
  captureUnhandled?: boolean;
318
+ /**
319
+ * Coleta CPU, memória, disco e recursos do processo: vai em cada heartbeat/poll (série da
320
+ * instância no app) e como `contexts.machine` em cada evento (como estava a máquina na hora
321
+ * do erro). Só em Node; sem custo de timer — pega carona no heartbeat. Default: true
322
+ */
323
+ machineMetrics?: boolean;
273
324
  /** Habilita heartbeat periódico (true usa defaults) */
274
325
  heartbeat?: boolean | HeartbeatOptions;
275
326
  /**
@@ -356,4 +407,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
356
407
 
357
408
  declare function scrubSecrets(text: string): string;
358
409
 
359
- export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventType, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, RunProgress, type RunProgressReporter, Scope, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
410
+ export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventType, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, MachineSnapshot, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, RunProgress, type RunProgressReporter, Scope, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Breadcrumb, EventUser, EventType, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunProgress, RunAttachmentContentType } from './protocol.js';
2
- export { BreadcrumbLevel, Envelope, EnvelopeItem, ExceptionData, InstanceInfo, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.js';
1
+ import { Breadcrumb, EventUser, EventType, MachineSnapshot, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunProgress, RunAttachmentContentType, EnvironmentInput } from './protocol.js';
2
+ export { BreadcrumbLevel, Envelope, EnvelopeItem, ExceptionData, InstanceInfo, MachineProcess, PollRequest, PollResponse, RunAttachmentConfirmRequest, RunAttachmentConfirmResponse, RunAttachmentCreateRequest, RunAttachmentCreateResponse, RunDescriptor, RunError, RunItemInput, RunItemsCreateRequest, RunItemsCreateResponse, RunLogEntry, RunStatus, RunUpdateRequest, RunUpdateResponse, RunUpdateStatus, StackFrame } from './protocol.js';
3
3
 
4
4
  /** Aceita tudo que o handler pode ter em mãos: bytes ou um caminho de arquivo (só Node). */
5
5
  type AttachInput = Uint8Array | ArrayBuffer | string;
@@ -47,15 +47,46 @@ interface Logger {
47
47
  error: (...args: unknown[]) => void;
48
48
  }
49
49
 
50
+ /**
51
+ * Coleta CPU/memória/disco da máquina e do processo — só em Node (no browser é no-op).
52
+ *
53
+ * Os módulos `node:*` entram por import dinâmico (o bundle é `platform: neutral`), e
54
+ * `sample()` é síncrono e barato: `os.cpus()` + `statfsSync` + `process.memoryUsage()`.
55
+ * Nunca lança — telemetria de recurso não pode derrubar o bot nem o capture de um erro.
56
+ */
57
+ declare class MachineSampler {
58
+ private readonly instanceId;
59
+ private readonly logger;
60
+ private os;
61
+ private fs;
62
+ private ready;
63
+ private prevCpu;
64
+ private prevProcessCpu;
65
+ private prevAt;
66
+ private lastCpuPercent;
67
+ private lastProcessCpuPercent;
68
+ constructor(instanceId: string, logger: Logger);
69
+ /** Carrega os módulos e tira a leitura-base de CPU (a primeira amostra ainda sai com cpu_percent null). */
70
+ start(): Promise<void>;
71
+ sample(): MachineSnapshot | undefined;
72
+ private cpuTotals;
73
+ /** Média desde a leitura anterior; leituras com menos de 1s de janela reutilizam a última média. */
74
+ private updateCpu;
75
+ private diskUsage;
76
+ }
77
+
50
78
  declare class Client {
51
79
  #private;
52
80
  readonly logger: Logger;
53
81
  /** Eco de lifecycle (Transaction.emit) e afins só imprimem no terminal com debug ligado. */
54
82
  readonly debug: boolean;
55
83
  private readonly transport;
84
+ private machine;
56
85
  constructor(options: InitOptions, config?: ResolvedConfig);
57
86
  get options(): InitOptions;
58
87
  get config(): ResolvedConfig;
88
+ /** Sampler de recursos da máquina — cada evento passa a levar `contexts.machine`. */
89
+ attachMachineSampler(sampler: MachineSampler | undefined): void;
59
90
  /** Serialização explícita nunca inclui o secret. */
60
91
  toJSON(): Record<string, unknown>;
61
92
  captureException(error: unknown, hint: CaptureHint | undefined, scope: Scope): string;
@@ -64,6 +95,12 @@ declare class Client {
64
95
  private prepareEvent;
65
96
  sendTransaction(payload: TransactionPayload): void;
66
97
  sendHeartbeat(payload: HeartbeatPayload): void;
98
+ /**
99
+ * Ambiente do item: o dele, senão o do init(), senão produção. Centralizado
100
+ * porque evento, transaction e heartbeat precisam responder igual — o servidor
101
+ * normaliza apelidos legados, então aqui basta repassar.
102
+ */
103
+ private resolveEnvironment;
67
104
  flush(timeoutMs?: number): Promise<boolean>;
68
105
  close(timeoutMs?: number): Promise<boolean>;
69
106
  }
@@ -131,7 +168,7 @@ declare class Transaction {
131
168
 
132
169
  /** Opções de tasks dentro de `init({ tasks: ... })`. Credencial (clientSecret), identidade (instanceId) e debug vêm do init. */
133
170
  interface TasksOptions {
134
- /** Intervalo entre polls sem trabalho. Default: 5_000 (mínimo 1_000) */
171
+ /** Intervalo entre polls sem trabalho. Default: 2_000 (mínimo 1_000) */
135
172
  pollIntervalMs?: number;
136
173
  /** Runs simultâneas (1..5). Default: 1 (sequencial) */
137
174
  concurrency?: number;
@@ -254,8 +291,16 @@ interface HeartbeatOptions {
254
291
  interface InitOptions {
255
292
  /** O Client Secret do agente (hb_…) — a mesma credencial para telemetria e tasks. Copie em Configurações → Agentes. */
256
293
  clientSecret: string;
257
- /** Default: 'production' */
258
- environment?: string;
294
+ /**
295
+ * Ambiente lógico onde este robô roda: 'development', 'testing', 'staging'
296
+ * (homologação) ou 'production'. Vai em todo evento e transaction E na
297
+ * identidade da instância — é por ele que as regras de alerta decidem se
298
+ * notificam. Default: 'production'.
299
+ *
300
+ * Um valor livre herdado continua valendo — o servidor normaliza apelidos
301
+ * como 'prod' e 'qa'.
302
+ */
303
+ environment?: EnvironmentInput;
259
304
  /** Versão do programa, ex.: 'robo-nfe@1.4.2' */
260
305
  release?: string;
261
306
  /** Identificador único e estável desta instância — a identidade do agente no painel (heartbeat e poll de tasks convergem nele) */
@@ -270,6 +315,12 @@ interface InitOptions {
270
315
  maxBreadcrumbs?: number;
271
316
  /** Registra handlers globais de erro (uncaughtException etc.). Default: true */
272
317
  captureUnhandled?: boolean;
318
+ /**
319
+ * Coleta CPU, memória, disco e recursos do processo: vai em cada heartbeat/poll (série da
320
+ * instância no app) e como `contexts.machine` em cada evento (como estava a máquina na hora
321
+ * do erro). Só em Node; sem custo de timer — pega carona no heartbeat. Default: true
322
+ */
323
+ machineMetrics?: boolean;
273
324
  /** Habilita heartbeat periódico (true usa defaults) */
274
325
  heartbeat?: boolean | HeartbeatOptions;
275
326
  /**
@@ -356,4 +407,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
356
407
 
357
408
  declare function scrubSecrets(text: string): string;
358
409
 
359
- export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventType, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, RunProgress, type RunProgressReporter, Scope, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
410
+ export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventType, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, MachineSnapshot, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, RunProgress, type RunProgressReporter, Scope, Step, StepData, type TaskHandler, type TasksOptions, Transaction, TransactionPayload, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, register, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withScope };
package/dist/index.js CHANGED
@@ -103,7 +103,7 @@ function scrubSecrets(text) {
103
103
 
104
104
  // src/utils.ts
105
105
  var SDK_NAME = "hobots";
106
- var SDK_VERSION = true ? "2.2.0" : "0.0.0-dev";
106
+ var SDK_VERSION = true ? "2.4.0" : "0.0.0-dev";
107
107
  function isNode() {
108
108
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
109
109
  }
@@ -396,6 +396,7 @@ var Client = class {
396
396
  #options;
397
397
  #config;
398
398
  transport;
399
+ machine;
399
400
  constructor(options, config = resolveConfig(options)) {
400
401
  this.#options = options;
401
402
  this.debug = !!options.debug;
@@ -415,6 +416,10 @@ var Client = class {
415
416
  get config() {
416
417
  return this.#config;
417
418
  }
419
+ /** Sampler de recursos da máquina — cada evento passa a levar `contexts.machine`. */
420
+ attachMachineSampler(sampler) {
421
+ this.machine = sampler;
422
+ }
418
423
  /** Serialização explícita nunca inclui o secret. */
419
424
  toJSON() {
420
425
  return { sdk: SDK_NAME, version: SDK_VERSION, baseUrl: this.#config.baseUrl };
@@ -446,15 +451,17 @@ var Client = class {
446
451
  prepareEvent(event, hint, scope2) {
447
452
  const tags = { ...this.options.tags, ...scope2.tags, ...event.tags, ...hint?.tags };
448
453
  const extra = { ...scope2.extra, ...event.extra, ...hint?.extra };
454
+ const sampled = this.machine?.sample();
455
+ const machine = sampled ? { machine: sampled } : {};
449
456
  return {
450
457
  ...event,
451
458
  type: hint?.type ?? event.type ?? scope2.type ?? "error",
452
459
  release: event.release ?? this.options.release,
453
- environment: event.environment ?? this.options.environment ?? "production",
460
+ environment: this.resolveEnvironment(event.environment),
454
461
  tags: Object.keys(tags).length > 0 ? tags : void 0,
455
462
  extra: Object.keys(extra).length > 0 ? extra : void 0,
456
463
  user: event.user ?? scope2.user,
457
- contexts: { ...runtimeContexts(), ...scope2.contexts, ...event.contexts },
464
+ contexts: { ...runtimeContexts(), ...machine, ...scope2.contexts, ...event.contexts },
458
465
  breadcrumbs: scope2.breadcrumbs.length > 0 ? [...scope2.breadcrumbs] : event.breadcrumbs,
459
466
  fingerprint: hint?.fingerprint ?? event.fingerprint ?? scope2.fingerprint
460
467
  };
@@ -465,7 +472,7 @@ var Client = class {
465
472
  payload: {
466
473
  ...payload,
467
474
  release: payload.release ?? this.options.release,
468
- environment: payload.environment ?? this.options.environment ?? "production",
475
+ environment: this.resolveEnvironment(payload.environment),
469
476
  tags: { ...this.options.tags, ...payload.tags }
470
477
  }
471
478
  });
@@ -473,9 +480,22 @@ var Client = class {
473
480
  sendHeartbeat(payload) {
474
481
  this.transport.enqueue({
475
482
  type: "heartbeat",
476
- payload: { ...payload, release: payload.release ?? this.options.release }
483
+ payload: {
484
+ ...payload,
485
+ release: payload.release ?? this.options.release,
486
+ // o ambiente viaja no beat: é assim que a instância se classifica no app
487
+ environment: this.resolveEnvironment(payload.environment)
488
+ }
477
489
  });
478
490
  }
491
+ /**
492
+ * Ambiente do item: o dele, senão o do init(), senão produção. Centralizado
493
+ * porque evento, transaction e heartbeat precisam responder igual — o servidor
494
+ * normaliza apelidos legados, então aqui basta repassar.
495
+ */
496
+ resolveEnvironment(value) {
497
+ return value ?? this.options.environment ?? "production";
498
+ }
479
499
  flush(timeoutMs) {
480
500
  return this.transport.flush(timeoutMs);
481
501
  }
@@ -489,8 +509,9 @@ var DEFAULT_INTERVAL_MS = 3e4;
489
509
  var MIN_REPORTED_INTERVAL_MS = 1e3;
490
510
  var MAX_REPORTED_INTERVAL_MS = 9e5;
491
511
  var Heartbeat = class {
492
- constructor(client2, options = {}, instanceId) {
512
+ constructor(client2, options = {}, instanceId, sampler) {
493
513
  this.client = client2;
514
+ this.sampler = sampler;
494
515
  this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
495
516
  this.instanceId = instanceId;
496
517
  this.name = options.name ?? instanceId;
@@ -529,7 +550,9 @@ var Heartbeat = class {
529
550
  name: this.name,
530
551
  interval_ms: Math.min(Math.max(this.intervalMs, MIN_REPORTED_INTERVAL_MS), MAX_REPORTED_INTERVAL_MS),
531
552
  timestamp: nowISO(),
532
- metadata: this.metadata
553
+ metadata: this.metadata,
554
+ // recursos da máquina a cada beat — a série da instância no app
555
+ machine: this.sampler?.sample()
533
556
  });
534
557
  }
535
558
  };
@@ -560,50 +583,184 @@ function hasInstalledGlobalHandlers() {
560
583
  return installed;
561
584
  }
562
585
  function installGlobalHandlers(client2, getScope) {
563
- if (installed) return;
586
+ if (installed) return () => void 0;
564
587
  installed = true;
565
588
  if (isNode()) {
566
- process.on(
567
- "uncaughtException",
568
- markInternal((error) => {
569
- client2.captureException(error, { type: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
570
- void client2.flush(2e3).then(async () => {
571
- if (externalListenerCount("uncaughtException") === 0) {
572
- await awaitEmergencyTasks(EMERGENCY_REPORT_CAP_MS);
573
- console.error(error);
574
- process.exit(1);
575
- }
576
- });
577
- })
578
- );
579
- process.on(
580
- "unhandledRejection",
581
- markInternal((reason) => {
582
- client2.captureException(reason, { type: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
583
- void client2.flush(2e3);
584
- })
585
- );
586
- process.on(
587
- "beforeExit",
588
- markInternal(() => {
589
- void client2.flush(2e3);
590
- })
591
- );
592
- return;
589
+ const onUncaught = markInternal((error) => {
590
+ client2.captureException(error, { type: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
591
+ void client2.flush(2e3).then(async () => {
592
+ if (externalListenerCount("uncaughtException") === 0) {
593
+ await awaitEmergencyTasks(EMERGENCY_REPORT_CAP_MS);
594
+ console.error(error);
595
+ process.exit(1);
596
+ }
597
+ });
598
+ });
599
+ const onRejection = markInternal((reason) => {
600
+ client2.captureException(reason, { type: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
601
+ void client2.flush(2e3);
602
+ });
603
+ const onBeforeExit = markInternal(() => {
604
+ void client2.flush(2e3);
605
+ });
606
+ process.on("uncaughtException", onUncaught);
607
+ process.on("unhandledRejection", onRejection);
608
+ process.on("beforeExit", onBeforeExit);
609
+ return () => {
610
+ process.off("uncaughtException", onUncaught);
611
+ process.off("unhandledRejection", onRejection);
612
+ process.off("beforeExit", onBeforeExit);
613
+ installed = false;
614
+ };
593
615
  }
594
616
  if (typeof window !== "undefined") {
595
- window.addEventListener("error", (event) => {
617
+ const onError = (event) => {
596
618
  client2.captureException(event.error ?? event.message, { tags: { mechanism: "onerror" } }, getScope());
597
- });
598
- window.addEventListener("unhandledrejection", (event) => {
619
+ };
620
+ const onRejection = (event) => {
599
621
  client2.captureException(event.reason, { tags: { mechanism: "onunhandledrejection" } }, getScope());
600
- });
601
- window.addEventListener("pagehide", () => {
622
+ };
623
+ const onPageHide = () => {
602
624
  void client2.flush(1e3);
603
- });
625
+ };
626
+ window.addEventListener("error", onError);
627
+ window.addEventListener("unhandledrejection", onRejection);
628
+ window.addEventListener("pagehide", onPageHide);
629
+ return () => {
630
+ window.removeEventListener("error", onError);
631
+ window.removeEventListener("unhandledrejection", onRejection);
632
+ window.removeEventListener("pagehide", onPageHide);
633
+ installed = false;
634
+ };
604
635
  }
636
+ return () => {
637
+ installed = false;
638
+ };
605
639
  }
606
640
 
641
+ // src/machine.ts
642
+ var MIN_CPU_WINDOW_MS = 1e3;
643
+ var round1 = (value) => Math.round(value * 10) / 10;
644
+ var clampPercent = (value) => Math.min(100, Math.max(0, value));
645
+ var MachineSampler = class {
646
+ constructor(instanceId, logger) {
647
+ this.instanceId = instanceId;
648
+ this.logger = logger;
649
+ }
650
+ os;
651
+ fs;
652
+ ready = false;
653
+ prevCpu;
654
+ prevProcessCpu;
655
+ prevAt = 0;
656
+ lastCpuPercent = null;
657
+ lastProcessCpuPercent = null;
658
+ /** Carrega os módulos e tira a leitura-base de CPU (a primeira amostra ainda sai com cpu_percent null). */
659
+ async start() {
660
+ if (!isNode()) return;
661
+ try {
662
+ this.os = await import("os");
663
+ this.fs = await import("fs");
664
+ this.prevCpu = this.cpuTotals();
665
+ this.prevProcessCpu = process.cpuUsage();
666
+ this.prevAt = Date.now();
667
+ this.ready = true;
668
+ } catch (error) {
669
+ this.logger.debug("m\xE9tricas de m\xE1quina indispon\xEDveis neste runtime", error);
670
+ }
671
+ }
672
+ sample() {
673
+ if (!this.ready || !this.os) return void 0;
674
+ try {
675
+ const os = this.os;
676
+ const cpuCount = Math.max(1, os.cpus().length);
677
+ this.updateCpu(cpuCount);
678
+ const snapshot = {
679
+ sampled_at: nowISO(),
680
+ instance_id: this.instanceId,
681
+ hostname: os.hostname(),
682
+ os: process.platform,
683
+ arch: process.arch,
684
+ cpu_count: cpuCount,
685
+ cpu_percent: this.lastCpuPercent,
686
+ process: {
687
+ pid: process.pid,
688
+ uptime_s: Math.round(process.uptime()),
689
+ cpu_percent: this.lastProcessCpuPercent
690
+ },
691
+ source: "stdlib"
692
+ };
693
+ if (process.platform !== "win32") {
694
+ const load = os.loadavg()[0];
695
+ if (typeof load === "number" && Number.isFinite(load)) snapshot.load_1 = round1(load);
696
+ }
697
+ const memTotal = os.totalmem();
698
+ const memFree = os.freemem();
699
+ if (memTotal > 0) {
700
+ snapshot.mem_total_bytes = memTotal;
701
+ snapshot.mem_available_bytes = memFree;
702
+ snapshot.mem_percent = round1(clampPercent((memTotal - memFree) / memTotal * 100));
703
+ }
704
+ const disk = this.diskUsage();
705
+ if (disk) Object.assign(snapshot, disk);
706
+ const mem = process.memoryUsage();
707
+ snapshot.process.rss_bytes = mem.rss;
708
+ snapshot.process.heap_used_bytes = mem.heapUsed;
709
+ return snapshot;
710
+ } catch (error) {
711
+ this.logger.debug("falha ao coletar m\xE9tricas de m\xE1quina", error);
712
+ return void 0;
713
+ }
714
+ }
715
+ cpuTotals() {
716
+ let busy = 0;
717
+ let total = 0;
718
+ for (const cpu of this.os.cpus()) {
719
+ const t = cpu.times;
720
+ const cpuBusy = t.user + t.nice + t.sys + t.irq;
721
+ busy += cpuBusy;
722
+ total += cpuBusy + t.idle;
723
+ }
724
+ return { busy, total };
725
+ }
726
+ /** Média desde a leitura anterior; leituras com menos de 1s de janela reutilizam a última média. */
727
+ updateCpu(cpuCount) {
728
+ const now = Date.now();
729
+ const elapsedMs = now - this.prevAt;
730
+ if (elapsedMs < MIN_CPU_WINDOW_MS) return;
731
+ const cpu = this.cpuTotals();
732
+ if (this.prevCpu) {
733
+ const total = cpu.total - this.prevCpu.total;
734
+ if (total > 0) this.lastCpuPercent = round1(clampPercent((cpu.busy - this.prevCpu.busy) / total * 100));
735
+ }
736
+ this.prevCpu = cpu;
737
+ const usage = process.cpuUsage(this.prevProcessCpu);
738
+ const usedMicros = usage.user + usage.system;
739
+ this.lastProcessCpuPercent = round1(clampPercent(usedMicros / (elapsedMs * 1e3 * cpuCount) * 100));
740
+ this.prevProcessCpu = process.cpuUsage();
741
+ this.prevAt = now;
742
+ }
743
+ diskUsage() {
744
+ const statfs = this.fs?.statfsSync;
745
+ if (typeof statfs !== "function") return void 0;
746
+ try {
747
+ const path = process.cwd();
748
+ const stat = statfs(path);
749
+ const total = stat.bsize * stat.blocks;
750
+ const free = stat.bsize * stat.bavail;
751
+ if (total <= 0) return void 0;
752
+ return {
753
+ disk_path: path,
754
+ disk_total_bytes: total,
755
+ disk_free_bytes: free,
756
+ disk_percent: round1(clampPercent((total - free) / total * 100))
757
+ };
758
+ } catch {
759
+ return void 0;
760
+ }
761
+ }
762
+ };
763
+
607
764
  // src/scope.ts
608
765
  var Scope = class _Scope {
609
766
  tags = {};
@@ -1787,7 +1944,7 @@ function installShutdownHandlers(agent2) {
1787
1944
  }
1788
1945
 
1789
1946
  // src/tasks/agent.ts
1790
- var DEFAULT_POLL_INTERVAL_MS = 5e3;
1947
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
1791
1948
  var MIN_POLL_INTERVAL_MS = 1e3;
1792
1949
  var MAX_CONCURRENCY = 5;
1793
1950
  var POLL_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
@@ -1918,11 +2075,17 @@ var TaskAgent = class {
1918
2075
  instance_id: this.instanceId,
1919
2076
  version: SDK_VERSION,
1920
2077
  host: hostname(),
2078
+ // ambiente da instância: em agentes poll-only (sem heartbeat) este é o
2079
+ // único canal por onde o servidor descobre onde o robô está rodando
2080
+ environment: this.options.environment,
1921
2081
  tasks: [...this.registry.keys()],
1922
2082
  // o servidor calcula a tolerância de offline (3×) a partir disto em
1923
2083
  // agentes poll-only — sem o campo, um poll lento flaparia offline/online.
1924
2084
  // Clampado à faixa aceita pelo schema do poll (1s..15min).
1925
- poll_interval_ms: Math.min(Math.max(this.pollIntervalMs, 1e3), 9e5)
2085
+ poll_interval_ms: Math.min(Math.max(this.pollIntervalMs, 1e3), 9e5),
2086
+ // recursos da máquina — alimenta a série da instância em agentes sem heartbeat
2087
+ // (o servidor grava no máx. 1 ponto/30s por instância)
2088
+ machine: this.options.machineSampler?.sample()
1926
2089
  },
1927
2090
  max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),
1928
2091
  tasks: [...this.registry.keys()]
@@ -2070,7 +2233,9 @@ var DEFAULT_MAX_BREADCRUMBS = 100;
2070
2233
  var client;
2071
2234
  var scope = new Scope();
2072
2235
  var heartbeat;
2236
+ var machineSampler;
2073
2237
  var agent;
2238
+ var uninstallGlobalHandlers;
2074
2239
  var registry = /* @__PURE__ */ new Map();
2075
2240
  var warnedTasksDisabled = false;
2076
2241
  function init(options) {
@@ -2083,21 +2248,26 @@ function init(options) {
2083
2248
  void agent.stop(0);
2084
2249
  agent = void 0;
2085
2250
  }
2251
+ uninstallGlobalHandlers?.();
2252
+ uninstallGlobalHandlers = void 0;
2086
2253
  warnedTasksDisabled = false;
2087
2254
  const config = resolveConfig(options);
2088
2255
  client = new Client(options, config);
2256
+ machineSampler = options.machineMetrics === false ? void 0 : new MachineSampler(options.instanceId, client.logger);
2257
+ void machineSampler?.start();
2258
+ client.attachMachineSampler(machineSampler);
2089
2259
  if (options.captureUnhandled !== false) {
2090
- installGlobalHandlers(client, () => scope);
2260
+ uninstallGlobalHandlers = installGlobalHandlers(client, () => scope);
2091
2261
  }
2092
2262
  if (options.heartbeat) {
2093
2263
  const heartbeatOptions = options.heartbeat === true ? {} : options.heartbeat;
2094
- heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId);
2264
+ heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId, machineSampler);
2095
2265
  heartbeat.start();
2096
2266
  }
2097
2267
  if (options.tasks) {
2098
2268
  const tasksOptions = options.tasks === true ? {} : options.tasks;
2099
2269
  agent = new TaskAgent(
2100
- { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client },
2270
+ { ...tasksOptions, config, instanceId: options.instanceId, environment: options.environment, debug: options.debug, client, machineSampler },
2101
2271
  registry
2102
2272
  );
2103
2273
  }