hobots 2.2.0 → 2.3.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 } 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;
@@ -270,6 +301,12 @@ interface InitOptions {
270
301
  maxBreadcrumbs?: number;
271
302
  /** Registra handlers globais de erro (uncaughtException etc.). Default: true */
272
303
  captureUnhandled?: boolean;
304
+ /**
305
+ * Coleta CPU, memória, disco e recursos do processo: vai em cada heartbeat/poll (série da
306
+ * instância no app) e como `contexts.machine` em cada evento (como estava a máquina na hora
307
+ * do erro). Só em Node; sem custo de timer — pega carona no heartbeat. Default: true
308
+ */
309
+ machineMetrics?: boolean;
273
310
  /** Habilita heartbeat periódico (true usa defaults) */
274
311
  heartbeat?: boolean | HeartbeatOptions;
275
312
  /**
@@ -356,4 +393,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
356
393
 
357
394
  declare function scrubSecrets(text: string): string;
358
395
 
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 };
396
+ 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 } 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;
@@ -270,6 +301,12 @@ interface InitOptions {
270
301
  maxBreadcrumbs?: number;
271
302
  /** Registra handlers globais de erro (uncaughtException etc.). Default: true */
272
303
  captureUnhandled?: boolean;
304
+ /**
305
+ * Coleta CPU, memória, disco e recursos do processo: vai em cada heartbeat/poll (série da
306
+ * instância no app) e como `contexts.machine` em cada evento (como estava a máquina na hora
307
+ * do erro). Só em Node; sem custo de timer — pega carona no heartbeat. Default: true
308
+ */
309
+ machineMetrics?: boolean;
273
310
  /** Habilita heartbeat periódico (true usa defaults) */
274
311
  heartbeat?: boolean | HeartbeatOptions;
275
312
  /**
@@ -356,4 +393,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
356
393
 
357
394
  declare function scrubSecrets(text: string): string;
358
395
 
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 };
396
+ 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.3.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,6 +451,8 @@ 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",
@@ -454,7 +461,7 @@ var Client = class {
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
  };
@@ -489,8 +496,9 @@ var DEFAULT_INTERVAL_MS = 3e4;
489
496
  var MIN_REPORTED_INTERVAL_MS = 1e3;
490
497
  var MAX_REPORTED_INTERVAL_MS = 9e5;
491
498
  var Heartbeat = class {
492
- constructor(client2, options = {}, instanceId) {
499
+ constructor(client2, options = {}, instanceId, sampler) {
493
500
  this.client = client2;
501
+ this.sampler = sampler;
494
502
  this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
495
503
  this.instanceId = instanceId;
496
504
  this.name = options.name ?? instanceId;
@@ -529,7 +537,9 @@ var Heartbeat = class {
529
537
  name: this.name,
530
538
  interval_ms: Math.min(Math.max(this.intervalMs, MIN_REPORTED_INTERVAL_MS), MAX_REPORTED_INTERVAL_MS),
531
539
  timestamp: nowISO(),
532
- metadata: this.metadata
540
+ metadata: this.metadata,
541
+ // recursos da máquina a cada beat — a série da instância no app
542
+ machine: this.sampler?.sample()
533
543
  });
534
544
  }
535
545
  };
@@ -560,50 +570,184 @@ function hasInstalledGlobalHandlers() {
560
570
  return installed;
561
571
  }
562
572
  function installGlobalHandlers(client2, getScope) {
563
- if (installed) return;
573
+ if (installed) return () => void 0;
564
574
  installed = true;
565
575
  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;
576
+ const onUncaught = markInternal((error) => {
577
+ client2.captureException(error, { type: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
578
+ void client2.flush(2e3).then(async () => {
579
+ if (externalListenerCount("uncaughtException") === 0) {
580
+ await awaitEmergencyTasks(EMERGENCY_REPORT_CAP_MS);
581
+ console.error(error);
582
+ process.exit(1);
583
+ }
584
+ });
585
+ });
586
+ const onRejection = markInternal((reason) => {
587
+ client2.captureException(reason, { type: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
588
+ void client2.flush(2e3);
589
+ });
590
+ const onBeforeExit = markInternal(() => {
591
+ void client2.flush(2e3);
592
+ });
593
+ process.on("uncaughtException", onUncaught);
594
+ process.on("unhandledRejection", onRejection);
595
+ process.on("beforeExit", onBeforeExit);
596
+ return () => {
597
+ process.off("uncaughtException", onUncaught);
598
+ process.off("unhandledRejection", onRejection);
599
+ process.off("beforeExit", onBeforeExit);
600
+ installed = false;
601
+ };
593
602
  }
594
603
  if (typeof window !== "undefined") {
595
- window.addEventListener("error", (event) => {
604
+ const onError = (event) => {
596
605
  client2.captureException(event.error ?? event.message, { tags: { mechanism: "onerror" } }, getScope());
597
- });
598
- window.addEventListener("unhandledrejection", (event) => {
606
+ };
607
+ const onRejection = (event) => {
599
608
  client2.captureException(event.reason, { tags: { mechanism: "onunhandledrejection" } }, getScope());
600
- });
601
- window.addEventListener("pagehide", () => {
609
+ };
610
+ const onPageHide = () => {
602
611
  void client2.flush(1e3);
603
- });
612
+ };
613
+ window.addEventListener("error", onError);
614
+ window.addEventListener("unhandledrejection", onRejection);
615
+ window.addEventListener("pagehide", onPageHide);
616
+ return () => {
617
+ window.removeEventListener("error", onError);
618
+ window.removeEventListener("unhandledrejection", onRejection);
619
+ window.removeEventListener("pagehide", onPageHide);
620
+ installed = false;
621
+ };
604
622
  }
623
+ return () => {
624
+ installed = false;
625
+ };
605
626
  }
606
627
 
628
+ // src/machine.ts
629
+ var MIN_CPU_WINDOW_MS = 1e3;
630
+ var round1 = (value) => Math.round(value * 10) / 10;
631
+ var clampPercent = (value) => Math.min(100, Math.max(0, value));
632
+ var MachineSampler = class {
633
+ constructor(instanceId, logger) {
634
+ this.instanceId = instanceId;
635
+ this.logger = logger;
636
+ }
637
+ os;
638
+ fs;
639
+ ready = false;
640
+ prevCpu;
641
+ prevProcessCpu;
642
+ prevAt = 0;
643
+ lastCpuPercent = null;
644
+ lastProcessCpuPercent = null;
645
+ /** Carrega os módulos e tira a leitura-base de CPU (a primeira amostra ainda sai com cpu_percent null). */
646
+ async start() {
647
+ if (!isNode()) return;
648
+ try {
649
+ this.os = await import("os");
650
+ this.fs = await import("fs");
651
+ this.prevCpu = this.cpuTotals();
652
+ this.prevProcessCpu = process.cpuUsage();
653
+ this.prevAt = Date.now();
654
+ this.ready = true;
655
+ } catch (error) {
656
+ this.logger.debug("m\xE9tricas de m\xE1quina indispon\xEDveis neste runtime", error);
657
+ }
658
+ }
659
+ sample() {
660
+ if (!this.ready || !this.os) return void 0;
661
+ try {
662
+ const os = this.os;
663
+ const cpuCount = Math.max(1, os.cpus().length);
664
+ this.updateCpu(cpuCount);
665
+ const snapshot = {
666
+ sampled_at: nowISO(),
667
+ instance_id: this.instanceId,
668
+ hostname: os.hostname(),
669
+ os: process.platform,
670
+ arch: process.arch,
671
+ cpu_count: cpuCount,
672
+ cpu_percent: this.lastCpuPercent,
673
+ process: {
674
+ pid: process.pid,
675
+ uptime_s: Math.round(process.uptime()),
676
+ cpu_percent: this.lastProcessCpuPercent
677
+ },
678
+ source: "stdlib"
679
+ };
680
+ if (process.platform !== "win32") {
681
+ const load = os.loadavg()[0];
682
+ if (typeof load === "number" && Number.isFinite(load)) snapshot.load_1 = round1(load);
683
+ }
684
+ const memTotal = os.totalmem();
685
+ const memFree = os.freemem();
686
+ if (memTotal > 0) {
687
+ snapshot.mem_total_bytes = memTotal;
688
+ snapshot.mem_available_bytes = memFree;
689
+ snapshot.mem_percent = round1(clampPercent((memTotal - memFree) / memTotal * 100));
690
+ }
691
+ const disk = this.diskUsage();
692
+ if (disk) Object.assign(snapshot, disk);
693
+ const mem = process.memoryUsage();
694
+ snapshot.process.rss_bytes = mem.rss;
695
+ snapshot.process.heap_used_bytes = mem.heapUsed;
696
+ return snapshot;
697
+ } catch (error) {
698
+ this.logger.debug("falha ao coletar m\xE9tricas de m\xE1quina", error);
699
+ return void 0;
700
+ }
701
+ }
702
+ cpuTotals() {
703
+ let busy = 0;
704
+ let total = 0;
705
+ for (const cpu of this.os.cpus()) {
706
+ const t = cpu.times;
707
+ const cpuBusy = t.user + t.nice + t.sys + t.irq;
708
+ busy += cpuBusy;
709
+ total += cpuBusy + t.idle;
710
+ }
711
+ return { busy, total };
712
+ }
713
+ /** Média desde a leitura anterior; leituras com menos de 1s de janela reutilizam a última média. */
714
+ updateCpu(cpuCount) {
715
+ const now = Date.now();
716
+ const elapsedMs = now - this.prevAt;
717
+ if (elapsedMs < MIN_CPU_WINDOW_MS) return;
718
+ const cpu = this.cpuTotals();
719
+ if (this.prevCpu) {
720
+ const total = cpu.total - this.prevCpu.total;
721
+ if (total > 0) this.lastCpuPercent = round1(clampPercent((cpu.busy - this.prevCpu.busy) / total * 100));
722
+ }
723
+ this.prevCpu = cpu;
724
+ const usage = process.cpuUsage(this.prevProcessCpu);
725
+ const usedMicros = usage.user + usage.system;
726
+ this.lastProcessCpuPercent = round1(clampPercent(usedMicros / (elapsedMs * 1e3 * cpuCount) * 100));
727
+ this.prevProcessCpu = process.cpuUsage();
728
+ this.prevAt = now;
729
+ }
730
+ diskUsage() {
731
+ const statfs = this.fs?.statfsSync;
732
+ if (typeof statfs !== "function") return void 0;
733
+ try {
734
+ const path = process.cwd();
735
+ const stat = statfs(path);
736
+ const total = stat.bsize * stat.blocks;
737
+ const free = stat.bsize * stat.bavail;
738
+ if (total <= 0) return void 0;
739
+ return {
740
+ disk_path: path,
741
+ disk_total_bytes: total,
742
+ disk_free_bytes: free,
743
+ disk_percent: round1(clampPercent((total - free) / total * 100))
744
+ };
745
+ } catch {
746
+ return void 0;
747
+ }
748
+ }
749
+ };
750
+
607
751
  // src/scope.ts
608
752
  var Scope = class _Scope {
609
753
  tags = {};
@@ -1922,7 +2066,10 @@ var TaskAgent = class {
1922
2066
  // o servidor calcula a tolerância de offline (3×) a partir disto em
1923
2067
  // agentes poll-only — sem o campo, um poll lento flaparia offline/online.
1924
2068
  // Clampado à faixa aceita pelo schema do poll (1s..15min).
1925
- poll_interval_ms: Math.min(Math.max(this.pollIntervalMs, 1e3), 9e5)
2069
+ poll_interval_ms: Math.min(Math.max(this.pollIntervalMs, 1e3), 9e5),
2070
+ // recursos da máquina — alimenta a série da instância em agentes sem heartbeat
2071
+ // (o servidor grava no máx. 1 ponto/30s por instância)
2072
+ machine: this.options.machineSampler?.sample()
1926
2073
  },
1927
2074
  max: Math.min(MAX_CONCURRENCY, this.concurrency - this.activeRuns.size),
1928
2075
  tasks: [...this.registry.keys()]
@@ -2070,7 +2217,9 @@ var DEFAULT_MAX_BREADCRUMBS = 100;
2070
2217
  var client;
2071
2218
  var scope = new Scope();
2072
2219
  var heartbeat;
2220
+ var machineSampler;
2073
2221
  var agent;
2222
+ var uninstallGlobalHandlers;
2074
2223
  var registry = /* @__PURE__ */ new Map();
2075
2224
  var warnedTasksDisabled = false;
2076
2225
  function init(options) {
@@ -2083,21 +2232,26 @@ function init(options) {
2083
2232
  void agent.stop(0);
2084
2233
  agent = void 0;
2085
2234
  }
2235
+ uninstallGlobalHandlers?.();
2236
+ uninstallGlobalHandlers = void 0;
2086
2237
  warnedTasksDisabled = false;
2087
2238
  const config = resolveConfig(options);
2088
2239
  client = new Client(options, config);
2240
+ machineSampler = options.machineMetrics === false ? void 0 : new MachineSampler(options.instanceId, client.logger);
2241
+ void machineSampler?.start();
2242
+ client.attachMachineSampler(machineSampler);
2089
2243
  if (options.captureUnhandled !== false) {
2090
- installGlobalHandlers(client, () => scope);
2244
+ uninstallGlobalHandlers = installGlobalHandlers(client, () => scope);
2091
2245
  }
2092
2246
  if (options.heartbeat) {
2093
2247
  const heartbeatOptions = options.heartbeat === true ? {} : options.heartbeat;
2094
- heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId);
2248
+ heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId, machineSampler);
2095
2249
  heartbeat.start();
2096
2250
  }
2097
2251
  if (options.tasks) {
2098
2252
  const tasksOptions = options.tasks === true ? {} : options.tasks;
2099
2253
  agent = new TaskAgent(
2100
- { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client },
2254
+ { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client, machineSampler },
2101
2255
  registry
2102
2256
  );
2103
2257
  }