hobots 1.5.0 → 1.6.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/README.md CHANGED
@@ -65,7 +65,7 @@ Cada projeto tem **um único Client Secret** — a mesma credencial autentica o
65
65
  hb_<64 hex>
66
66
  ```
67
67
 
68
- (Chaves antigas, 64 hex puros, continuam válidas no servidor.) A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL. Copie o Client Secret no app, em **Configurações → Projetos** — os snippets de integração já vêm prontos. O SDK fala com a API oficial `https://api2.hobots.app` automaticamente; em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL` (ex.: `HOBOTS_API_URL=http://localhost:3001`).
68
+ A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL. Copie o Client Secret no app, em **Configurações → Projetos** — os snippets de integração já vêm prontos. O SDK fala com a API oficial `https://api2.hobots.app` automaticamente; em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL` (ex.: `HOBOTS_API_URL=http://localhost:3001`).
69
69
 
70
70
  ---
71
71
 
@@ -354,37 +354,7 @@ Para parar de aceitar execuções **sem** derrubar a telemetria, use `stop(timeo
354
354
  | `hobots` | Tudo: `init`, Monitor (captura, transactions, crons, heartbeat), Manager (`register`/`start`/`stop`), `close`, classes e tipos. |
355
355
  | `hobots/protocol` | Tipos do wire format (Monitor e Manager). |
356
356
 
357
- Empacotado em **ESM e CommonJS** com **definições de tipos** (`.d.ts`) para todas as entradas. (`hobots/manager/protocol` segue existindo como alias transitório de tipos.)
358
-
359
- ---
360
-
361
- ## Migração da API antiga (`hobots/manager`)
362
-
363
- O Manager foi unificado no import raiz — `hobots/manager` **foi removido**:
364
-
365
- ```diff
366
- -import * as Monitor from 'hobots';
367
- -import * as Manager from 'hobots/manager';
368
- +import * as Hobots from 'hobots';
369
-
370
- -Monitor.init({ dsn, serverName: 'vm-01', heartbeat: true });
371
- -Manager.init({ dsn, agentName: 'vm-01' });
372
- +Hobots.init({ clientSecret, instanceId: 'vm-01', heartbeat: true, manager: true });
373
-
374
- -Manager.register('minha-task', handler);
375
- -Manager.start();
376
- +Hobots.register('minha-task', handler);
377
- +Hobots.start();
378
-
379
- -await Manager.stop(15_000);
380
- -await Monitor.close(3_000);
381
- +await Hobots.close(15_000);
382
- ```
383
-
384
- - A `dsn` (mecanismo antigo, `https://<secretKey>@host/<slug>`) não existe mais — passe `clientSecret` (em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL`). Copie o Client Secret em **Configurações → Projetos**.
385
- - `agentName` e `serverName` não existem mais — a identidade do agente é o `instanceId` (obrigatório, sem fallback).
386
- - As opções do Manager (`pollIntervalMs`, `concurrency`, `taskTimeoutMs`, `logFlushIntervalMs`, `cancelCheckIntervalMs`, `installExitHandlers`) vão no campo `manager` do `init()`; `clientSecret` e `debug` não se repetem.
387
- - Tipos (`ManagerTaskHandler`, `RunContext`, `ManagerOptions`) e classes (`ManagerAgent`, `ManagerApi`) agora vêm de `hobots`; o wire format vem de `hobots/protocol`.
357
+ Empacotado em **ESM e CommonJS** com **definições de tipos** (`.d.ts`) para todas as entradas.
388
358
 
389
359
  ---
390
360
 
package/dist/index.cjs CHANGED
@@ -48,6 +48,7 @@ __export(src_exports, {
48
48
  init: () => init,
49
49
  register: () => register,
50
50
  resolveConfig: () => resolveConfig,
51
+ scrubSecrets: () => scrubSecrets,
51
52
  setContext: () => setContext,
52
53
  setExtra: () => setExtra,
53
54
  setTag: () => setTag,
@@ -63,21 +64,47 @@ module.exports = __toCommonJS(src_exports);
63
64
 
64
65
  // src/config.ts
65
66
  var DEFAULT_BASE_URL = "https://api2.hobots.app";
67
+ var SECRET_PATTERN = /^(hb_)?[a-f0-9]{64}$/i;
66
68
  function envBaseUrl() {
67
69
  if (typeof process === "undefined" || !process.env) return void 0;
68
70
  return process.env.HOBOTS_API_URL?.trim() || void 0;
69
71
  }
72
+ function isLoopbackHost(hostname2) {
73
+ return hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.startsWith("127.") || hostname2 === "[::1]" || hostname2 === "::1" || hostname2 === "0.0.0.0";
74
+ }
75
+ var warnedInsecureBaseUrl = false;
76
+ function validateBaseUrl(raw, prefix) {
77
+ let url;
78
+ try {
79
+ url = new URL(raw);
80
+ } catch {
81
+ throw new Error(`${prefix} HOBOTS_API_URL inv\xE1lida \u2014 esperado URL http(s), ex.: http://localhost:3001`);
82
+ }
83
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
84
+ throw new Error(`${prefix} HOBOTS_API_URL com esquema "${url.protocol}" n\xE3o suportado \u2014 use http(s)`);
85
+ }
86
+ if (url.username || url.password) {
87
+ throw new Error(`${prefix} HOBOTS_API_URL n\xE3o pode conter credenciais na URL \u2014 a autentica\xE7\xE3o \xE9 s\xF3 pelo Client Secret`);
88
+ }
89
+ if (url.protocol === "http:" && !isLoopbackHost(url.hostname) && !warnedInsecureBaseUrl) {
90
+ warnedInsecureBaseUrl = true;
91
+ console.warn(
92
+ `${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafega sem TLS`
93
+ );
94
+ }
95
+ return raw.replace(/\/+$/, "");
96
+ }
70
97
  function resolveConfig(options, prefix = "[hobots]") {
71
98
  const key = options.clientSecret?.trim();
72
99
  if (!key) {
73
100
  throw new Error(`${prefix} clientSecret ausente \u2014 copie o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos`);
74
101
  }
75
- const baseUrl = (envBaseUrl() ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
76
- try {
77
- new URL(baseUrl);
78
- } catch {
79
- throw new Error(`${prefix} HOBOTS_API_URL inv\xE1lida: "${baseUrl}" \u2014 esperado ex.: http://localhost:3001`);
102
+ if (!SECRET_PATTERN.test(key)) {
103
+ throw new Error(
104
+ `${prefix} clientSecret com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos`
105
+ );
80
106
  }
107
+ const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
81
108
  return {
82
109
  key,
83
110
  baseUrl,
@@ -119,9 +146,28 @@ function parseStacktrace(stack) {
119
146
  return frames;
120
147
  }
121
148
 
149
+ // src/scrub.ts
150
+ var SECRET_PATTERNS = [
151
+ // client secret da Hobots
152
+ [/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
153
+ // tokens bearer em headers/mensagens
154
+ [/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
155
+ // chaves no formato sk-… (OpenAI e afins)
156
+ [/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
157
+ // credenciais embutidas em URL (https://user:pass@host)
158
+ [/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
159
+ ];
160
+ function scrubSecrets(text) {
161
+ let out = text;
162
+ for (const [pattern, replacement] of SECRET_PATTERNS) {
163
+ out = out.replace(pattern, replacement);
164
+ }
165
+ return out;
166
+ }
167
+
122
168
  // src/utils.ts
123
169
  var SDK_NAME = "hobots";
124
- var SDK_VERSION = true ? "1.5.0" : "0.0.0-dev";
170
+ var SDK_VERSION = true ? "1.6.0" : "0.0.0-dev";
125
171
  function isNode() {
126
172
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
127
173
  }
@@ -130,6 +176,12 @@ function uuid4() {
130
176
  if (cryptoRef && typeof cryptoRef.randomUUID === "function") {
131
177
  return cryptoRef.randomUUID().replace(/-/g, "");
132
178
  }
179
+ if (cryptoRef && typeof cryptoRef.getRandomValues === "function") {
180
+ const bytes = cryptoRef.getRandomValues(new Uint8Array(16));
181
+ bytes[6] = bytes[6] & 15 | 64;
182
+ bytes[8] = bytes[8] & 63 | 128;
183
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
184
+ }
133
185
  let out = "";
134
186
  for (let i = 0; i < 32; i++) {
135
187
  out += Math.floor(Math.random() * 16).toString(16);
@@ -148,7 +200,42 @@ function sleep(ms) {
148
200
  unrefTimer(timer);
149
201
  });
150
202
  }
151
- var PREFIX = "[hobots-monitor]";
203
+ function jitter(baseMs) {
204
+ return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
205
+ }
206
+ var MAX_RETRY_AFTER_MS = 6e4;
207
+ function retryAfterMs(response) {
208
+ const header = response.headers.get("retry-after");
209
+ if (!header) return void 0;
210
+ const seconds = Number(header);
211
+ if (Number.isFinite(seconds)) {
212
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, seconds * 1e3));
213
+ }
214
+ const date = Date.parse(header);
215
+ if (Number.isNaN(date)) return void 0;
216
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, date - Date.now()));
217
+ }
218
+ function withTimeoutSignal(timeoutMs, external) {
219
+ const controller = new AbortController();
220
+ const onAbort = () => controller.abort(external?.reason);
221
+ if (external?.aborted) {
222
+ onAbort();
223
+ } else {
224
+ external?.addEventListener("abort", onAbort, { once: true });
225
+ }
226
+ const timer = setTimeout(() => {
227
+ controller.abort(Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms`), { name: "TimeoutError" }));
228
+ }, timeoutMs);
229
+ unrefTimer(timer);
230
+ return {
231
+ signal: controller.signal,
232
+ cleanup: () => {
233
+ clearTimeout(timer);
234
+ external?.removeEventListener("abort", onAbort);
235
+ }
236
+ };
237
+ }
238
+ var PREFIX = "[hobots]";
152
239
  function makeLogger(enabled, prefix = PREFIX) {
153
240
  return {
154
241
  debug: (...args) => enabled && console.debug(colorize(prefix, "gray"), ...args),
@@ -161,7 +248,8 @@ var ANSI = {
161
248
  gray: "\x1B[90m",
162
249
  cyan: "\x1B[36m",
163
250
  yellow: "\x1B[33m",
164
- red: "\x1B[31m"
251
+ red: "\x1B[31m",
252
+ green: "\x1B[32m"
165
253
  };
166
254
  function colorEnabled() {
167
255
  if (!isNode()) return false;
@@ -188,11 +276,11 @@ function eventFromException(error, level = "error") {
188
276
  let frames = [];
189
277
  if (error instanceof Error) {
190
278
  type = error.name || "Error";
191
- value = error.message;
279
+ value = scrubSecrets(error.message);
192
280
  frames = parseStacktrace(error.stack);
193
281
  } else {
194
282
  type = "NonError";
195
- value = safeString(error);
283
+ value = scrubSecrets(safeString(error));
196
284
  }
197
285
  return {
198
286
  event_id: uuid4(),
@@ -210,12 +298,13 @@ function eventFromMessage(message, level = "info") {
210
298
  event_id: uuid4(),
211
299
  timestamp: nowISO(),
212
300
  level,
213
- message
301
+ message: scrubSecrets(message)
214
302
  };
215
303
  }
216
304
 
217
305
  // src/transport.ts
218
306
  var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
307
+ var REQUEST_TIMEOUT_MS = 1e4;
219
308
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
220
309
  var DEFAULT_MAX_BATCH_SIZE = 10;
221
310
  var DEFAULT_MAX_QUEUE_SIZE = 200;
@@ -282,11 +371,13 @@ var Transport = class {
282
371
  }
283
372
  async sendWithRetry(envelope) {
284
373
  for (let attempt = 0; ; attempt++) {
374
+ let serverDelay;
285
375
  try {
286
376
  const response = await fetch(this.endpoint, {
287
377
  method: "POST",
288
378
  headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
289
- body: JSON.stringify(envelope)
379
+ body: JSON.stringify(envelope),
380
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
290
381
  });
291
382
  if (response.ok) {
292
383
  this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
@@ -296,13 +387,15 @@ var Transport = class {
296
387
  this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
297
388
  return;
298
389
  }
390
+ if (response.status === 429) serverDelay = retryAfterMs(response);
299
391
  throw new Error(`HTTP ${response.status}`);
300
392
  } catch (error) {
301
- const delay = RETRY_DELAYS_MS[attempt];
302
- if (delay === void 0) {
393
+ const base = RETRY_DELAYS_MS[attempt];
394
+ if (base === void 0) {
303
395
  this.logger.error("envelope descartado ap\xF3s retries:", error);
304
396
  return;
305
397
  }
398
+ const delay = serverDelay ?? jitter(base);
306
399
  this.logger.warn(`falha no envio (${String(error)}), retry em ${delay}ms`);
307
400
  await sleep(delay);
308
401
  }
@@ -332,22 +425,38 @@ function runtimeContexts() {
332
425
  return { runtime: { name: "browser" } };
333
426
  }
334
427
  var Client = class {
335
- options;
336
- config;
337
428
  logger;
429
+ // campos privados + getters de protótipo: o clientSecret não aparece em
430
+ // JSON.stringify/serialização enumerável do Client (ex.: um objeto capturado
431
+ // que referencie o client), mas client.options/config seguem funcionando
432
+ #options;
433
+ #config;
338
434
  transport;
339
435
  constructor(options) {
340
- this.options = options;
436
+ this.#options = options;
341
437
  this.logger = makeLogger(!!options.debug);
342
- this.config = resolveConfig(options);
438
+ this.#config = resolveConfig(options);
343
439
  this.transport = new Transport({
344
- endpoint: this.config.ingestEndpoint,
345
- key: this.config.key,
440
+ endpoint: this.#config.ingestEndpoint,
441
+ key: this.#config.key,
346
442
  logger: this.logger,
347
443
  flushIntervalMs: options.flushIntervalMs,
348
444
  maxBatchSize: options.maxBatchSize
349
445
  });
350
446
  }
447
+ get options() {
448
+ return this.#options;
449
+ }
450
+ get config() {
451
+ return this.#config;
452
+ }
453
+ /** Serialização explícita nunca inclui o secret. */
454
+ toJSON() {
455
+ return { sdk: SDK_NAME, version: SDK_VERSION, baseUrl: this.#config.baseUrl };
456
+ }
457
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
458
+ return `Client { baseUrl: '${this.#config.baseUrl}' }`;
459
+ }
351
460
  captureException(error, hint, scope2) {
352
461
  return this.captureEvent(eventFromException(error, hint?.level ?? "error"), hint, scope2);
353
462
  }
@@ -601,6 +710,15 @@ var Scope = class _Scope {
601
710
  function spanId() {
602
711
  return uuid4().slice(0, 16);
603
712
  }
713
+ function printSpanLog(kind, phase, label, startTs, endTs) {
714
+ const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
715
+ const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? "green" : "cyan");
716
+ const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
717
+ console.log(`${tag} ${label}${suffix}`);
718
+ }
719
+ function spanLabel(op, description) {
720
+ return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
721
+ }
604
722
  var Span = class {
605
723
  constructor(transaction, parentSpanId, op, description) {
606
724
  this.transaction = transaction;
@@ -626,7 +744,9 @@ var Span = class {
626
744
  return this.transaction.createSpan(this.spanId, op, description);
627
745
  }
628
746
  finish() {
629
- if (!this.endTimestamp) this.endTimestamp = nowISO();
747
+ if (this.endTimestamp) return;
748
+ this.endTimestamp = nowISO();
749
+ printSpanLog("span", "finish", spanLabel(this.op, this.description), this.startTimestamp, this.endTimestamp);
630
750
  }
631
751
  toJSON() {
632
752
  return {
@@ -648,6 +768,7 @@ var Transaction = class {
648
768
  this.op = op;
649
769
  this.sampled = sampled;
650
770
  this.runId = runId;
771
+ printSpanLog("transaction", "start", op ? `${name} (${op})` : name);
651
772
  }
652
773
  eventId = uuid4();
653
774
  spanId = spanId();
@@ -659,6 +780,7 @@ var Transaction = class {
659
780
  createSpan(parentSpanId, op, description) {
660
781
  const span = new Span(this, parentSpanId, op, description);
661
782
  this.spans.push(span);
783
+ printSpanLog("span", "start", spanLabel(op, description));
662
784
  return span;
663
785
  }
664
786
  /** Span direto da transaction. */
@@ -681,6 +803,14 @@ var Transaction = class {
681
803
  if (this.finished) return;
682
804
  this.finished = true;
683
805
  for (const span of this.spans) span.finish();
806
+ const endTimestamp = nowISO();
807
+ printSpanLog(
808
+ "transaction",
809
+ "finish",
810
+ this.op ? `${this.name} (${this.op})` : this.name,
811
+ this.startTimestamp,
812
+ endTimestamp
813
+ );
684
814
  if (!this.sampled || !this.client) return;
685
815
  const payload = {
686
816
  event_id: this.eventId,
@@ -688,7 +818,7 @@ var Transaction = class {
688
818
  op: this.op,
689
819
  status: this.status,
690
820
  start_timestamp: this.startTimestamp,
691
- timestamp: nowISO(),
821
+ timestamp: endTimestamp,
692
822
  spans: this.spans.map((span) => span.toJSON()),
693
823
  tags: Object.keys(this.tags).length > 0 ? this.tags : void 0,
694
824
  run_id: this.runId
@@ -745,6 +875,8 @@ async function withMonitorImpl(client2, captureError, monitorSlug, fn, options)
745
875
 
746
876
  // src/manager/api.ts
747
877
  var RETRY_DELAYS_MS2 = [1e3, 4e3, 16e3];
878
+ var POLL_TIMEOUT_MS = 3e4;
879
+ var UPDATE_TIMEOUT_MS = 1e4;
748
880
  var ManagerApiError = class extends Error {
749
881
  constructor(message, status) {
750
882
  super(message);
@@ -769,16 +901,39 @@ var ManagerApi = class {
769
901
  }
770
902
  /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
771
903
  async poll(request, signal) {
772
- const response = await fetch(`${this.baseUrl}/poll`, {
773
- method: "POST",
774
- headers: this.headers(),
775
- body: JSON.stringify(request),
776
- signal
777
- });
904
+ const { signal: timeoutSignal, cleanup } = withTimeoutSignal(POLL_TIMEOUT_MS, signal);
905
+ let response;
906
+ try {
907
+ response = await fetch(`${this.baseUrl}/poll`, {
908
+ method: "POST",
909
+ headers: this.headers(),
910
+ body: JSON.stringify(request),
911
+ signal: timeoutSignal
912
+ });
913
+ } finally {
914
+ cleanup();
915
+ }
778
916
  if (!response.ok) {
779
917
  throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
780
918
  }
781
- return await response.json();
919
+ return this.parsePollResponse(await response.json(), request.max ?? 1);
920
+ }
921
+ /** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
922
+ parsePollResponse(body, max) {
923
+ const runs = body?.runs;
924
+ if (!Array.isArray(runs)) {
925
+ throw new ManagerApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
926
+ }
927
+ const valid = [];
928
+ for (const run of runs.slice(0, Math.max(0, max))) {
929
+ if (typeof run?.id !== "string" || typeof run.task !== "string") continue;
930
+ const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
931
+ valid.push({ ...run, id: run.id, task: run.task, params });
932
+ }
933
+ if (valid.length < runs.length) {
934
+ this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
935
+ }
936
+ return { runs: valid };
782
937
  }
783
938
  /**
784
939
  * PATCH de status/logs com retry em falha de rede/5xx/429.
@@ -787,25 +942,29 @@ var ManagerApi = class {
787
942
  async updateRun(runId, update) {
788
943
  for (let attempt = 0; ; attempt++) {
789
944
  let retriable;
945
+ let serverDelay;
790
946
  try {
791
947
  const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
792
948
  method: "PATCH",
793
949
  headers: this.headers(),
794
- body: JSON.stringify(update)
950
+ body: JSON.stringify(update),
951
+ signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
795
952
  });
796
953
  if (response.ok) return;
797
954
  if (response.status >= 400 && response.status < 500 && response.status !== 429) {
798
955
  throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
799
956
  }
957
+ if (response.status === 429) serverDelay = retryAfterMs(response);
800
958
  retriable = new Error(`HTTP ${response.status}`);
801
959
  } catch (error) {
802
960
  if (error instanceof ManagerApiError) throw error;
803
961
  retriable = error;
804
962
  }
805
- const delay = RETRY_DELAYS_MS2[attempt];
806
- if (delay === void 0) {
963
+ const base = RETRY_DELAYS_MS2[attempt];
964
+ if (base === void 0) {
807
965
  throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
808
966
  }
967
+ const delay = serverDelay ?? jitter(base);
809
968
  this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
810
969
  await sleep(delay);
811
970
  }
@@ -830,11 +989,12 @@ var LOG_LEVEL_COLOR = {
830
989
  debug: "gray",
831
990
  info: "cyan",
832
991
  warning: "yellow",
833
- error: "red"
992
+ error: "red",
993
+ success: "green"
834
994
  };
835
995
  function printRunLog(task, level, message) {
836
996
  const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
837
- const method = level === "warning" ? "warn" : level;
997
+ const method = level === "warning" ? "warn" : level === "success" ? "info" : level;
838
998
  console[method](line, message);
839
999
  }
840
1000
  function runCanceledError() {
@@ -879,7 +1039,8 @@ var RunReporter = class {
879
1039
  debug: (message) => this.log("debug", message),
880
1040
  info: (message) => this.log("info", message),
881
1041
  warn: (message) => this.log("warning", message),
882
- error: (message) => this.log("error", message)
1042
+ error: (message) => this.log("error", message),
1043
+ success: (message) => this.log("success", message)
883
1044
  };
884
1045
  const signal = this.abortController.signal;
885
1046
  this.context = {
@@ -927,13 +1088,14 @@ var RunReporter = class {
927
1088
  }
928
1089
  log(level, message) {
929
1090
  if (this.dead) return;
930
- printRunLog(this.context.task, level, message);
1091
+ const scrubbed = scrubSecrets(message);
1092
+ printRunLog(this.context.task, level, scrubbed);
931
1093
  if (this.loggedCount >= MAX_LOGS_PER_RUN) {
932
1094
  this.droppedCount++;
933
1095
  return;
934
1096
  }
935
1097
  this.loggedCount++;
936
- const truncated = message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : message;
1098
+ const truncated = scrubbed.length > MAX_LOG_MESSAGE_CHARS ? `${scrubbed.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : scrubbed;
937
1099
  this.buffer.push({ ts: nowISO(), level, message: truncated });
938
1100
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
939
1101
  this.flushLogs();
@@ -1117,8 +1279,8 @@ var ManagerAgent = class {
1117
1279
  constructor(options, registry2) {
1118
1280
  this.options = options;
1119
1281
  this.registry = registry2;
1120
- this.logger = makeLogger(options.debug ?? false, "[hobots-manager]");
1121
- const config = resolveConfig(options, "[hobots-manager]");
1282
+ this.logger = makeLogger(options.debug ?? false, "[hobots]");
1283
+ const config = resolveConfig(options, "[hobots]");
1122
1284
  this.api = new ManagerApi({ baseUrl: config.managerBaseUrl, key: config.key, logger: this.logger });
1123
1285
  this.client = options.client;
1124
1286
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
@@ -1152,10 +1314,10 @@ var ManagerAgent = class {
1152
1314
  start() {
1153
1315
  if (this.state === "started") return;
1154
1316
  if (this.state === "stopping") {
1155
- throw new Error("[hobots-manager] start() chamado durante stop() \u2014 aguarde o stop terminar");
1317
+ throw new Error("[hobots] start() chamado durante stop() \u2014 aguarde o stop terminar");
1156
1318
  }
1157
1319
  if (this.registry.size === 0) {
1158
- throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
1320
+ throw new Error("[hobots] nenhuma task registrada \u2014 chame register() antes de start()");
1159
1321
  }
1160
1322
  this.state = "started";
1161
1323
  if (this.options.installExitHandlers !== false && !this.uninstallShutdown) {
@@ -1237,6 +1399,7 @@ var ManagerAgent = class {
1237
1399
  this.consecutiveFailures = 0;
1238
1400
  this.consecutiveAuthFailures = 0;
1239
1401
  for (const run of response.runs) {
1402
+ if (this.activeRuns.size >= this.concurrency) break;
1240
1403
  this.dispatch(run);
1241
1404
  }
1242
1405
  this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);
@@ -1257,7 +1420,7 @@ var ManagerAgent = class {
1257
1420
  }
1258
1421
  this.consecutiveFailures++;
1259
1422
  const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)];
1260
- const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16e3));
1423
+ const delay = Math.min(Math.max(jitter(ladder), 0), Math.max(this.pollIntervalMs, 16e3));
1261
1424
  this.logger.warn(`poll falhou (${String(error)}), pr\xF3xima tentativa em ${delay}ms`);
1262
1425
  return delay;
1263
1426
  }
@@ -1269,7 +1432,7 @@ var ManagerAgent = class {
1269
1432
  this.activeRuns.add(promise);
1270
1433
  }
1271
1434
  async executeRun(run) {
1272
- const tx = new Transaction(this.client, `run:${run.task}`, "manager.run", true, run.id);
1435
+ const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", true, run.id);
1273
1436
  const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx, this.logFlushIntervalMs, this.cancelCheckIntervalMs);
1274
1437
  this.reporters.add(reporter);
1275
1438
  try {
@@ -1335,11 +1498,11 @@ function toRunError(error) {
1335
1498
  if (error instanceof Error) {
1336
1499
  return {
1337
1500
  type: error.name,
1338
- message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),
1339
- stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : void 0
1501
+ message: scrubSecrets(truncate(error.message, MAX_ERROR_MESSAGE_CHARS)),
1502
+ stack: error.stack ? scrubSecrets(truncate(error.stack, MAX_ERROR_STACK_CHARS)) : void 0
1340
1503
  };
1341
1504
  }
1342
- return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };
1505
+ return { message: scrubSecrets(truncate(String(error), MAX_ERROR_MESSAGE_CHARS)) };
1343
1506
  }
1344
1507
 
1345
1508
  // src/core.ts