hobots 2.0.0 → 2.2.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, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunAttachmentContentType } from './protocol.cjs';
2
- export { 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, 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';
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;
@@ -25,7 +25,7 @@ declare class Scope {
25
25
  user?: EventUser;
26
26
  contexts: Record<string, Record<string, unknown>>;
27
27
  extra: Record<string, unknown>;
28
- level?: Severity;
28
+ type?: EventType;
29
29
  fingerprint?: string[];
30
30
  breadcrumbs: Breadcrumb[];
31
31
  setTag(key: string, value: string): this;
@@ -34,7 +34,7 @@ declare class Scope {
34
34
  setContext(name: string, context: Record<string, unknown> | null): this;
35
35
  setExtra(key: string, value: unknown): this;
36
36
  setExtras(extras: Record<string, unknown>): this;
37
- setLevel(level: Severity): this;
37
+ setType(type: EventType): this;
38
38
  setFingerprint(fingerprint: string[]): this;
39
39
  addBreadcrumb(crumb: BreadcrumbInput, maxBreadcrumbs: number): this;
40
40
  clearBreadcrumbs(): this;
@@ -59,7 +59,7 @@ declare class Client {
59
59
  /** Serialização explícita nunca inclui o secret. */
60
60
  toJSON(): Record<string, unknown>;
61
61
  captureException(error: unknown, hint: CaptureHint | undefined, scope: Scope): string;
62
- captureMessage(message: string, level: Severity, hint: CaptureHint | undefined, scope: Scope): string;
62
+ captureMessage(message: string, type: EventType, hint: CaptureHint | undefined, scope: Scope): string;
63
63
  captureEvent(event: EventPayload, hint: CaptureHint | undefined, scope: Scope): string;
64
64
  private prepareEvent;
65
65
  sendTransaction(payload: TransactionPayload): void;
@@ -198,6 +198,24 @@ interface RunItems {
198
198
  /** Ocorrência de negócio (ex.: "nota nº 10 não encontrada") — não é falha do bot. */
199
199
  occurrence(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
200
200
  }
201
+ /**
202
+ * Progresso da execução (itens processados / total) — opcional e independente de
203
+ * `ctx.items`. Declare o total antes do loop e avance a cada item: a plataforma e o
204
+ * Studio mostram barra, percentual e tempo restante estimado. Sem `total()` nada é
205
+ * enviado. Chamadas são síncronas e baratas (≤ 1 PATCH/s, pegando carona nos logs).
206
+ */
207
+ interface RunProgressReporter {
208
+ /** Declara (ou ajusta) o total de itens previsto — inteiro >= 1. Idempotente. */
209
+ total(total: number): void;
210
+ /** Incrementa os processados (padrão 1). Clampa em `total`. */
211
+ advance(count?: number): void;
212
+ /** Define o valor absoluto de processados e, opcionalmente, o total. */
213
+ set(current: number, total?: number): void;
214
+ /** Processados até agora (0 antes de qualquer chamada). */
215
+ readonly current: number;
216
+ /** `{ current, total }` quando há total declarado; null caso contrário. */
217
+ readonly snapshot: RunProgress | null;
218
+ }
201
219
  interface RunContext {
202
220
  runId: string;
203
221
  task: string;
@@ -205,6 +223,8 @@ interface RunContext {
205
223
  log: RunLogger;
206
224
  /** Itens processados/gerados pela execução — a entrega do bot, com status e atributos */
207
225
  items: RunItems;
226
+ /** Progresso declarado (barra + ETA no app) — opcional, independente de `ctx.items` */
227
+ progress: RunProgressReporter;
208
228
  /**
209
229
  * Transaction de performance desta execução — já correlacionada à run e finalizada
210
230
  * automaticamente ao fim do handler. Use `ctx.tx.startChild(op, description)` pra medir
@@ -277,7 +297,7 @@ interface CaptureAttachment {
277
297
  contentType?: RunAttachmentContentType;
278
298
  }
279
299
  interface CaptureHint {
280
- level?: Severity;
300
+ type?: EventType;
281
301
  tags?: Record<string, string>;
282
302
  extra?: Record<string, unknown>;
283
303
  fingerprint?: string[];
@@ -305,7 +325,7 @@ declare function getClient(): Client | undefined;
305
325
  * como miniatura na linha de log da run correspondente ao erro.
306
326
  */
307
327
  declare function captureException(error: unknown, hint?: CaptureHint): string | undefined;
308
- declare function captureMessage(message: string, level?: Severity, hint?: CaptureHint): string | undefined;
328
+ declare function captureMessage(message: string, type?: EventType, hint?: CaptureHint): string | undefined;
309
329
  /** Registra um breadcrumb — anexado aos próximos eventos como trilha do que aconteceu antes. */
310
330
  declare function addBreadcrumb(crumb: BreadcrumbInput): void;
311
331
  declare function setTag(key: string, value: string): void;
@@ -336,4 +356,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
336
356
 
337
357
  declare function scrubSecrets(text: string): string;
338
358
 
339
- export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, Scope, Severity, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Breadcrumb, EventUser, Severity, EventPayload, TransactionPayload, HeartbeatPayload, StepStatus, RunLogLevel, StepData, RunItemStatus, RunAttachmentContentType } from './protocol.js';
2
- export { 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, 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';
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;
@@ -25,7 +25,7 @@ declare class Scope {
25
25
  user?: EventUser;
26
26
  contexts: Record<string, Record<string, unknown>>;
27
27
  extra: Record<string, unknown>;
28
- level?: Severity;
28
+ type?: EventType;
29
29
  fingerprint?: string[];
30
30
  breadcrumbs: Breadcrumb[];
31
31
  setTag(key: string, value: string): this;
@@ -34,7 +34,7 @@ declare class Scope {
34
34
  setContext(name: string, context: Record<string, unknown> | null): this;
35
35
  setExtra(key: string, value: unknown): this;
36
36
  setExtras(extras: Record<string, unknown>): this;
37
- setLevel(level: Severity): this;
37
+ setType(type: EventType): this;
38
38
  setFingerprint(fingerprint: string[]): this;
39
39
  addBreadcrumb(crumb: BreadcrumbInput, maxBreadcrumbs: number): this;
40
40
  clearBreadcrumbs(): this;
@@ -59,7 +59,7 @@ declare class Client {
59
59
  /** Serialização explícita nunca inclui o secret. */
60
60
  toJSON(): Record<string, unknown>;
61
61
  captureException(error: unknown, hint: CaptureHint | undefined, scope: Scope): string;
62
- captureMessage(message: string, level: Severity, hint: CaptureHint | undefined, scope: Scope): string;
62
+ captureMessage(message: string, type: EventType, hint: CaptureHint | undefined, scope: Scope): string;
63
63
  captureEvent(event: EventPayload, hint: CaptureHint | undefined, scope: Scope): string;
64
64
  private prepareEvent;
65
65
  sendTransaction(payload: TransactionPayload): void;
@@ -198,6 +198,24 @@ interface RunItems {
198
198
  /** Ocorrência de negócio (ex.: "nota nº 10 não encontrada") — não é falha do bot. */
199
199
  occurrence(message: string, payload?: Record<string, unknown>, options?: RunItemOptions): void;
200
200
  }
201
+ /**
202
+ * Progresso da execução (itens processados / total) — opcional e independente de
203
+ * `ctx.items`. Declare o total antes do loop e avance a cada item: a plataforma e o
204
+ * Studio mostram barra, percentual e tempo restante estimado. Sem `total()` nada é
205
+ * enviado. Chamadas são síncronas e baratas (≤ 1 PATCH/s, pegando carona nos logs).
206
+ */
207
+ interface RunProgressReporter {
208
+ /** Declara (ou ajusta) o total de itens previsto — inteiro >= 1. Idempotente. */
209
+ total(total: number): void;
210
+ /** Incrementa os processados (padrão 1). Clampa em `total`. */
211
+ advance(count?: number): void;
212
+ /** Define o valor absoluto de processados e, opcionalmente, o total. */
213
+ set(current: number, total?: number): void;
214
+ /** Processados até agora (0 antes de qualquer chamada). */
215
+ readonly current: number;
216
+ /** `{ current, total }` quando há total declarado; null caso contrário. */
217
+ readonly snapshot: RunProgress | null;
218
+ }
201
219
  interface RunContext {
202
220
  runId: string;
203
221
  task: string;
@@ -205,6 +223,8 @@ interface RunContext {
205
223
  log: RunLogger;
206
224
  /** Itens processados/gerados pela execução — a entrega do bot, com status e atributos */
207
225
  items: RunItems;
226
+ /** Progresso declarado (barra + ETA no app) — opcional, independente de `ctx.items` */
227
+ progress: RunProgressReporter;
208
228
  /**
209
229
  * Transaction de performance desta execução — já correlacionada à run e finalizada
210
230
  * automaticamente ao fim do handler. Use `ctx.tx.startChild(op, description)` pra medir
@@ -277,7 +297,7 @@ interface CaptureAttachment {
277
297
  contentType?: RunAttachmentContentType;
278
298
  }
279
299
  interface CaptureHint {
280
- level?: Severity;
300
+ type?: EventType;
281
301
  tags?: Record<string, string>;
282
302
  extra?: Record<string, unknown>;
283
303
  fingerprint?: string[];
@@ -305,7 +325,7 @@ declare function getClient(): Client | undefined;
305
325
  * como miniatura na linha de log da run correspondente ao erro.
306
326
  */
307
327
  declare function captureException(error: unknown, hint?: CaptureHint): string | undefined;
308
- declare function captureMessage(message: string, level?: Severity, hint?: CaptureHint): string | undefined;
328
+ declare function captureMessage(message: string, type?: EventType, hint?: CaptureHint): string | undefined;
309
329
  /** Registra um breadcrumb — anexado aos próximos eventos como trilha do que aconteceu antes. */
310
330
  declare function addBreadcrumb(crumb: BreadcrumbInput): void;
311
331
  declare function setTag(key: string, value: string): void;
@@ -336,4 +356,4 @@ declare function close(timeoutMs?: number): Promise<boolean>;
336
356
 
337
357
  declare function scrubSecrets(text: string): string;
338
358
 
339
- export { type AttachInput, Breadcrumb, type BreadcrumbInput, type CaptureAttachment, type CaptureHint, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, RunAttachmentContentType, type RunContext, type RunItemOptions, RunItemStatus, type RunItems, RunLogLevel, Scope, Severity, 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 };
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 };
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.0.0" : "0.0.0-dev";
106
+ var SDK_VERSION = true ? "2.2.0" : "0.0.0-dev";
107
107
  function isNode() {
108
108
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
109
109
  }
@@ -233,34 +233,34 @@ function safeString(value) {
233
233
  }
234
234
  return String(value);
235
235
  }
236
- function eventFromException(error, level = "error") {
237
- let type;
236
+ function eventFromException(error, type = "error") {
237
+ let exceptionType;
238
238
  let value;
239
239
  let frames = [];
240
240
  if (error instanceof Error) {
241
- type = error.name || "Error";
241
+ exceptionType = error.name || "Error";
242
242
  value = scrubSecrets(error.message);
243
243
  frames = parseStacktrace(error.stack);
244
244
  } else {
245
- type = "NonError";
245
+ exceptionType = "NonError";
246
246
  value = scrubSecrets(safeString(error));
247
247
  }
248
248
  return {
249
249
  event_id: uuid4(),
250
250
  timestamp: nowISO(),
251
- level,
251
+ type,
252
252
  exception: {
253
- type,
253
+ type: exceptionType,
254
254
  value,
255
255
  ...frames.length > 0 ? { stacktrace: { frames } } : {}
256
256
  }
257
257
  };
258
258
  }
259
- function eventFromMessage(message, level = "info") {
259
+ function eventFromMessage(message, type = "warning") {
260
260
  return {
261
261
  event_id: uuid4(),
262
262
  timestamp: nowISO(),
263
- level,
263
+ type,
264
264
  message: scrubSecrets(message)
265
265
  };
266
266
  }
@@ -423,10 +423,10 @@ var Client = class {
423
423
  return `Client { baseUrl: '${this.#config.baseUrl}' }`;
424
424
  }
425
425
  captureException(error, hint, scope2) {
426
- return this.captureEvent(eventFromException(error, hint?.level ?? "error"), hint, scope2);
426
+ return this.captureEvent(eventFromException(error, hint?.type ?? "error"), hint, scope2);
427
427
  }
428
- captureMessage(message, level, hint, scope2) {
429
- return this.captureEvent(eventFromMessage(message, level), hint, scope2);
428
+ captureMessage(message, type, hint, scope2) {
429
+ return this.captureEvent(eventFromMessage(message, type), hint, scope2);
430
430
  }
431
431
  captureEvent(event, hint, scope2) {
432
432
  const sampleRate = this.options.sampleRate ?? 1;
@@ -448,7 +448,7 @@ var Client = class {
448
448
  const extra = { ...scope2.extra, ...event.extra, ...hint?.extra };
449
449
  return {
450
450
  ...event,
451
- level: hint?.level ?? event.level ?? scope2.level ?? "error",
451
+ type: hint?.type ?? event.type ?? scope2.type ?? "error",
452
452
  release: event.release ?? this.options.release,
453
453
  environment: event.environment ?? this.options.environment ?? "production",
454
454
  tags: Object.keys(tags).length > 0 ? tags : void 0,
@@ -566,7 +566,7 @@ function installGlobalHandlers(client2, getScope) {
566
566
  process.on(
567
567
  "uncaughtException",
568
568
  markInternal((error) => {
569
- client2.captureException(error, { level: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
569
+ client2.captureException(error, { type: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
570
570
  void client2.flush(2e3).then(async () => {
571
571
  if (externalListenerCount("uncaughtException") === 0) {
572
572
  await awaitEmergencyTasks(EMERGENCY_REPORT_CAP_MS);
@@ -579,7 +579,7 @@ function installGlobalHandlers(client2, getScope) {
579
579
  process.on(
580
580
  "unhandledRejection",
581
581
  markInternal((reason) => {
582
- client2.captureException(reason, { level: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
582
+ client2.captureException(reason, { type: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
583
583
  void client2.flush(2e3);
584
584
  })
585
585
  );
@@ -610,7 +610,7 @@ var Scope = class _Scope {
610
610
  user;
611
611
  contexts = {};
612
612
  extra = {};
613
- level;
613
+ type;
614
614
  fingerprint;
615
615
  breadcrumbs = [];
616
616
  setTag(key, value) {
@@ -641,8 +641,8 @@ var Scope = class _Scope {
641
641
  Object.assign(this.extra, extras);
642
642
  return this;
643
643
  }
644
- setLevel(level) {
645
- this.level = level;
644
+ setType(type) {
645
+ this.type = type;
646
646
  return this;
647
647
  }
648
648
  setFingerprint(fingerprint) {
@@ -666,7 +666,7 @@ var Scope = class _Scope {
666
666
  next.user = this.user ? { ...this.user } : void 0;
667
667
  next.contexts = Object.fromEntries(Object.entries(this.contexts).map(([k, v]) => [k, { ...v }]));
668
668
  next.extra = { ...this.extra };
669
- next.level = this.level;
669
+ next.type = this.type;
670
670
  next.fingerprint = this.fingerprint ? [...this.fingerprint] : void 0;
671
671
  next.breadcrumbs = [...this.breadcrumbs];
672
672
  return next;
@@ -1143,6 +1143,7 @@ var MAX_ITEM_MESSAGE_CHARS = 2e3;
1143
1143
  var MAX_ITEM_PAYLOAD_BYTES = 16384;
1144
1144
  var ITEMS_FLUSH_AT_BUFFERED = 100;
1145
1145
  var ITEMS_DRAIN_MS = 15e3;
1146
+ var PROGRESS_FLUSH_MS = 1e3;
1146
1147
  function scrubPayloadValue(value, depth) {
1147
1148
  if (typeof value === "string") return scrubSecrets(value);
1148
1149
  if (depth >= 4 || value === null || typeof value !== "object") return value;
@@ -1177,12 +1178,28 @@ var RunReporter = class {
1177
1178
  failed: (message, payload, options) => this.addItem("failed", message, payload, options?.id),
1178
1179
  occurrence: (message, payload, options) => this.addItem("occurrence", message, payload, options?.id)
1179
1180
  };
1181
+ const progress = Object.defineProperties(
1182
+ {
1183
+ total: (total) => this.setProgress(this.progressCurrent, total),
1184
+ advance: (count = 1) => this.setProgress(this.progressCurrent + count, this.progressTotal),
1185
+ set: (current, total) => this.setProgress(current, total ?? this.progressTotal)
1186
+ },
1187
+ {
1188
+ current: { enumerable: true, get: () => this.progressCurrent },
1189
+ snapshot: {
1190
+ enumerable: true,
1191
+ get: () => this.progressTotal == null ? null : { current: this.progressCurrent, total: this.progressTotal }
1192
+ }
1193
+ }
1194
+ // defineProperties não tipa os getters adicionados — a interface é a fonte de verdade
1195
+ );
1180
1196
  const signal = this.abortController.signal;
1181
1197
  this.context = {
1182
1198
  runId,
1183
1199
  task,
1184
1200
  log,
1185
1201
  items,
1202
+ progress,
1186
1203
  tx,
1187
1204
  signal,
1188
1205
  throwIfCanceled: () => {
@@ -1226,6 +1243,14 @@ var RunReporter = class {
1226
1243
  uploadQueue = [];
1227
1244
  /** Uma vez que a instalação recusa anexos, para de tentar (e de avisar) nesta run. */
1228
1245
  attachmentsUnsupported = false;
1246
+ // Progresso (ctx.progress): snapshot absoluto, enviado com throttle via piggyback
1247
+ // em `enqueue()` — nunca gera tráfego próprio enquanto logs/heartbeat já fluem.
1248
+ progressCurrent = 0;
1249
+ progressTotal = null;
1250
+ /** Snapshot ainda não entregue ao servidor. */
1251
+ progressDirty = false;
1252
+ progressTimer;
1253
+ progressWarned = false;
1229
1254
  get isDead() {
1230
1255
  return this.dead;
1231
1256
  }
@@ -1243,6 +1268,7 @@ var RunReporter = class {
1243
1268
  this.abort();
1244
1269
  this.dead = true;
1245
1270
  this.clearFlushTimer();
1271
+ this.clearProgressTimer();
1246
1272
  this.buffer = [];
1247
1273
  this.itemsClosed = true;
1248
1274
  this.clearItemsTimer();
@@ -1322,13 +1348,63 @@ var RunReporter = class {
1322
1348
  reportSuccess() {
1323
1349
  this.stopCancelWatch();
1324
1350
  this.clearFlushTimer();
1351
+ this.forceFinalProgress();
1325
1352
  this.enqueue({ status: "succeeded", logs: this.takeLogs() });
1326
1353
  }
1327
1354
  reportFailure(error) {
1328
1355
  this.stopCancelWatch();
1329
1356
  this.clearFlushTimer();
1357
+ this.forceFinalProgress();
1330
1358
  this.enqueue({ status: "failed", error, logs: this.takeLogs() });
1331
1359
  }
1360
+ // ── Progresso ────────────────────────────────────────────────────────────────
1361
+ /** Valida, clampa e marca o snapshot como pendente; agenda flush só se nada mais vai sair. */
1362
+ setProgress(current, total) {
1363
+ if (this.dead) return;
1364
+ if (total != null && (!Number.isInteger(total) || total < 1)) {
1365
+ if (!this.progressWarned) {
1366
+ this.progressWarned = true;
1367
+ this.logger.warn(`ctx.progress: total inv\xE1lido (${String(total)}) \u2014 precisa ser inteiro >= 1; ignorado`);
1368
+ }
1369
+ return;
1370
+ }
1371
+ const safeCurrent = Number.isFinite(current) ? Math.max(0, Math.floor(current)) : this.progressCurrent;
1372
+ const next = total == null ? safeCurrent : Math.min(safeCurrent, total);
1373
+ if (next === this.progressCurrent && total === this.progressTotal) return;
1374
+ this.progressCurrent = next;
1375
+ this.progressTotal = total;
1376
+ if (total == null) return;
1377
+ this.progressDirty = true;
1378
+ if (this.progressTimer) return;
1379
+ this.progressTimer = setTimeout(() => {
1380
+ this.progressTimer = void 0;
1381
+ this.flushProgress();
1382
+ }, PROGRESS_FLUSH_MS);
1383
+ unrefTimer(this.progressTimer);
1384
+ }
1385
+ flushProgress() {
1386
+ this.clearProgressTimer();
1387
+ if (this.dead || !this.progressDirty) return;
1388
+ if (this.buffer.length > 0) this.flushLogs();
1389
+ else this.enqueue({ status: "running" });
1390
+ }
1391
+ clearProgressTimer() {
1392
+ if (this.progressTimer) {
1393
+ clearTimeout(this.progressTimer);
1394
+ this.progressTimer = void 0;
1395
+ }
1396
+ }
1397
+ /** O PATCH terminal sempre leva o snapshot final (o app mostra "87/100" numa run que falhou). */
1398
+ forceFinalProgress() {
1399
+ this.clearProgressTimer();
1400
+ this.progressDirty = this.progressTotal != null;
1401
+ }
1402
+ /** Consome o snapshot pendente para pegar carona no próximo PATCH. */
1403
+ takeProgress() {
1404
+ if (!this.progressDirty || this.progressTotal == null) return void 0;
1405
+ this.progressDirty = false;
1406
+ return { current: this.progressCurrent, total: this.progressTotal };
1407
+ }
1332
1408
  /**
1333
1409
  * Heartbeat de cancelamento: enquanto o handler roda, um PATCH 'running' vazio
1334
1410
  * periódico faz o servidor responder 409 se a run foi cancelada no app — o que
@@ -1591,11 +1667,14 @@ var RunReporter = class {
1591
1667
  new Promise((resolve) => unrefTimer(setTimeout(resolve, timeoutMs)))
1592
1668
  ]);
1593
1669
  }
1594
- enqueue(update) {
1670
+ enqueue(updateIn) {
1595
1671
  if (this.dead) {
1596
- this.salvageLogs(update.logs);
1672
+ this.salvageLogs(updateIn.logs);
1597
1673
  return;
1598
1674
  }
1675
+ this.clearProgressTimer();
1676
+ const progress = this.takeProgress();
1677
+ const update = progress ? { ...updateIn, progress } : updateIn;
1599
1678
  this.pendingUpdates++;
1600
1679
  this.chain = this.chain.then(async () => {
1601
1680
  if (this.dead) {
@@ -1621,6 +1700,7 @@ var RunReporter = class {
1621
1700
  this.buffer.unshift(...update.logs);
1622
1701
  this.scheduleFlush();
1623
1702
  }
1703
+ if (update.progress && this.progressTotal != null) this.progressDirty = true;
1624
1704
  this.logger.error(`update da run ${this.runId} perdido:`, error);
1625
1705
  }
1626
1706
  }).finally(() => {
@@ -2026,10 +2106,10 @@ function init(options) {
2026
2106
  function getClient() {
2027
2107
  return client;
2028
2108
  }
2029
- function toRunLogLevel(level) {
2030
- return level === "fatal" ? "error" : level;
2109
+ function toRunLogLevel(type) {
2110
+ return type === "fatal" ? "error" : type;
2031
2111
  }
2032
- function forwardAttachment(attachment, message, level) {
2112
+ function forwardAttachment(attachment, message, type) {
2033
2113
  const attach = currentRunAttach();
2034
2114
  if (!attach) {
2035
2115
  client?.logger.warn("attachment descartado \u2014 s\xF3 \xE9 suportado dentro de uma execu\xE7\xE3o de task (register/start)");
@@ -2038,7 +2118,7 @@ function forwardAttachment(attachment, message, level) {
2038
2118
  const withMeta = typeof attachment === "object" && "image" in attachment ? attachment : { image: attachment };
2039
2119
  void attach(withMeta.image, {
2040
2120
  message,
2041
- level: toRunLogLevel(level),
2121
+ level: toRunLogLevel(type),
2042
2122
  caption: withMeta.caption,
2043
2123
  contentType: withMeta.contentType
2044
2124
  });
@@ -2046,15 +2126,15 @@ function forwardAttachment(attachment, message, level) {
2046
2126
  function captureException(error, hint) {
2047
2127
  if (hint?.attachment) {
2048
2128
  const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
2049
- forwardAttachment(hint.attachment, message, hint.level ?? "error");
2129
+ forwardAttachment(hint.attachment, message, hint.type ?? "error");
2050
2130
  }
2051
2131
  return client?.captureException(error, hint, scope);
2052
2132
  }
2053
- function captureMessage(message, level = "info", hint) {
2133
+ function captureMessage(message, type = "warning", hint) {
2054
2134
  if (hint?.attachment) {
2055
- forwardAttachment(hint.attachment, message, hint.level ?? level);
2135
+ forwardAttachment(hint.attachment, message, hint.type ?? type);
2056
2136
  }
2057
- return client?.captureMessage(message, level, hint, scope);
2137
+ return client?.captureMessage(message, type, hint, scope);
2058
2138
  }
2059
2139
  function addBreadcrumb(crumb) {
2060
2140
  scope.addBreadcrumb(crumb, client?.options.maxBreadcrumbs ?? DEFAULT_MAX_BREADCRUMBS);