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/dist/index.d.ts CHANGED
@@ -52,11 +52,14 @@ interface Logger {
52
52
  }
53
53
 
54
54
  declare class Client {
55
- readonly options: InitOptions;
56
- readonly config: ResolvedConfig;
55
+ #private;
57
56
  readonly logger: Logger;
58
57
  private readonly transport;
59
58
  constructor(options: InitOptions);
59
+ get options(): InitOptions;
60
+ get config(): ResolvedConfig;
61
+ /** Serialização explícita nunca inclui o secret. */
62
+ toJSON(): Record<string, unknown>;
60
63
  captureException(error: unknown, hint: CaptureHint | undefined, scope: Scope): string;
61
64
  captureMessage(message: string, level: Severity, hint: CaptureHint | undefined, scope: Scope): string;
62
65
  captureEvent(event: EventPayload, hint: CaptureHint | undefined, scope: Scope): string;
@@ -159,6 +162,7 @@ interface RunLogger {
159
162
  info(message: string): void;
160
163
  warn(message: string): void;
161
164
  error(message: string): void;
165
+ success(message: string): void;
162
166
  }
163
167
  interface RunContext {
164
168
  runId: string;
@@ -286,6 +290,8 @@ declare function stop(timeoutMs?: number): Promise<boolean>;
286
290
  */
287
291
  declare function close(timeoutMs?: number): Promise<boolean>;
288
292
 
293
+ declare function scrubSecrets(text: string): string;
294
+
289
295
  /**
290
296
  * Loop de polling do Manager: busca runs pendentes, executa os handlers registrados
291
297
  * e reporta status/logs/result. Ao contrário do Monitor, o timer de polling NÃO é
@@ -350,6 +356,8 @@ declare class ManagerApi {
350
356
  private headers;
351
357
  /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
352
358
  poll(request: ManagerPollRequest, signal?: AbortSignal): Promise<ManagerPollResponse>;
359
+ /** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
360
+ private parsePollResponse;
353
361
  /**
354
362
  * PATCH de status/logs com retry em falha de rede/5xx/429.
355
363
  * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
@@ -363,4 +371,4 @@ declare class ManagerApi {
363
371
  updateRunOnce(runId: string, update: ManagerRunUpdateRequest, timeoutMs?: number): Promise<void>;
364
372
  }
365
373
 
366
- export { Breadcrumb, type BreadcrumbInput, type CaptureHint, type CheckInOptions, CheckInPayload, CheckInStatus, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, ManagerAgent, ManagerApi, ManagerApiError, type ManagerOptions, ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest, type ManagerTaskHandler, type ResolvedConfig, type RunContext, Scope, Severity, Span, SpanData, Transaction, TransactionPayload, type WithMonitorOptions, addBreadcrumb, captureException, captureMessage, checkIn, close, flush, getClient, init, register, resolveConfig, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withMonitor, withScope };
374
+ export { Breadcrumb, type BreadcrumbInput, type CaptureHint, type CheckInOptions, CheckInPayload, CheckInStatus, Client, DEFAULT_BASE_URL, EventPayload, EventUser, type HeartbeatOptions, HeartbeatPayload, type InitOptions, ManagerAgent, ManagerApi, ManagerApiError, type ManagerOptions, ManagerPollRequest, ManagerPollResponse, ManagerRunUpdateRequest, type ManagerTaskHandler, type ResolvedConfig, type RunContext, Scope, Severity, Span, SpanData, Transaction, TransactionPayload, type WithMonitorOptions, addBreadcrumb, captureException, captureMessage, checkIn, close, flush, getClient, init, register, resolveConfig, scrubSecrets, setContext, setExtra, setTag, setTags, setUser, start, startTransaction, stop, withMonitor, withScope };
package/dist/index.js CHANGED
@@ -1,20 +1,46 @@
1
1
  // src/config.ts
2
2
  var DEFAULT_BASE_URL = "https://api2.hobots.app";
3
+ var SECRET_PATTERN = /^(hb_)?[a-f0-9]{64}$/i;
3
4
  function envBaseUrl() {
4
5
  if (typeof process === "undefined" || !process.env) return void 0;
5
6
  return process.env.HOBOTS_API_URL?.trim() || void 0;
6
7
  }
8
+ function isLoopbackHost(hostname2) {
9
+ return hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.startsWith("127.") || hostname2 === "[::1]" || hostname2 === "::1" || hostname2 === "0.0.0.0";
10
+ }
11
+ var warnedInsecureBaseUrl = false;
12
+ function validateBaseUrl(raw, prefix) {
13
+ let url;
14
+ try {
15
+ url = new URL(raw);
16
+ } catch {
17
+ throw new Error(`${prefix} HOBOTS_API_URL inv\xE1lida \u2014 esperado URL http(s), ex.: http://localhost:3001`);
18
+ }
19
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
20
+ throw new Error(`${prefix} HOBOTS_API_URL com esquema "${url.protocol}" n\xE3o suportado \u2014 use http(s)`);
21
+ }
22
+ if (url.username || url.password) {
23
+ 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`);
24
+ }
25
+ if (url.protocol === "http:" && !isLoopbackHost(url.hostname) && !warnedInsecureBaseUrl) {
26
+ warnedInsecureBaseUrl = true;
27
+ console.warn(
28
+ `${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafega sem TLS`
29
+ );
30
+ }
31
+ return raw.replace(/\/+$/, "");
32
+ }
7
33
  function resolveConfig(options, prefix = "[hobots]") {
8
34
  const key = options.clientSecret?.trim();
9
35
  if (!key) {
10
36
  throw new Error(`${prefix} clientSecret ausente \u2014 copie o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos`);
11
37
  }
12
- const baseUrl = (envBaseUrl() ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
13
- try {
14
- new URL(baseUrl);
15
- } catch {
16
- throw new Error(`${prefix} HOBOTS_API_URL inv\xE1lida: "${baseUrl}" \u2014 esperado ex.: http://localhost:3001`);
38
+ if (!SECRET_PATTERN.test(key)) {
39
+ throw new Error(
40
+ `${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`
41
+ );
17
42
  }
43
+ const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
18
44
  return {
19
45
  key,
20
46
  baseUrl,
@@ -56,9 +82,28 @@ function parseStacktrace(stack) {
56
82
  return frames;
57
83
  }
58
84
 
85
+ // src/scrub.ts
86
+ var SECRET_PATTERNS = [
87
+ // client secret da Hobots
88
+ [/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
89
+ // tokens bearer em headers/mensagens
90
+ [/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
91
+ // chaves no formato sk-… (OpenAI e afins)
92
+ [/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
93
+ // credenciais embutidas em URL (https://user:pass@host)
94
+ [/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
95
+ ];
96
+ function scrubSecrets(text) {
97
+ let out = text;
98
+ for (const [pattern, replacement] of SECRET_PATTERNS) {
99
+ out = out.replace(pattern, replacement);
100
+ }
101
+ return out;
102
+ }
103
+
59
104
  // src/utils.ts
60
105
  var SDK_NAME = "hobots";
61
- var SDK_VERSION = true ? "1.5.0" : "0.0.0-dev";
106
+ var SDK_VERSION = true ? "1.6.0" : "0.0.0-dev";
62
107
  function isNode() {
63
108
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
64
109
  }
@@ -67,6 +112,12 @@ function uuid4() {
67
112
  if (cryptoRef && typeof cryptoRef.randomUUID === "function") {
68
113
  return cryptoRef.randomUUID().replace(/-/g, "");
69
114
  }
115
+ if (cryptoRef && typeof cryptoRef.getRandomValues === "function") {
116
+ const bytes = cryptoRef.getRandomValues(new Uint8Array(16));
117
+ bytes[6] = bytes[6] & 15 | 64;
118
+ bytes[8] = bytes[8] & 63 | 128;
119
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
120
+ }
70
121
  let out = "";
71
122
  for (let i = 0; i < 32; i++) {
72
123
  out += Math.floor(Math.random() * 16).toString(16);
@@ -85,7 +136,42 @@ function sleep(ms) {
85
136
  unrefTimer(timer);
86
137
  });
87
138
  }
88
- var PREFIX = "[hobots-monitor]";
139
+ function jitter(baseMs) {
140
+ return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
141
+ }
142
+ var MAX_RETRY_AFTER_MS = 6e4;
143
+ function retryAfterMs(response) {
144
+ const header = response.headers.get("retry-after");
145
+ if (!header) return void 0;
146
+ const seconds = Number(header);
147
+ if (Number.isFinite(seconds)) {
148
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, seconds * 1e3));
149
+ }
150
+ const date = Date.parse(header);
151
+ if (Number.isNaN(date)) return void 0;
152
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, date - Date.now()));
153
+ }
154
+ function withTimeoutSignal(timeoutMs, external) {
155
+ const controller = new AbortController();
156
+ const onAbort = () => controller.abort(external?.reason);
157
+ if (external?.aborted) {
158
+ onAbort();
159
+ } else {
160
+ external?.addEventListener("abort", onAbort, { once: true });
161
+ }
162
+ const timer = setTimeout(() => {
163
+ controller.abort(Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms`), { name: "TimeoutError" }));
164
+ }, timeoutMs);
165
+ unrefTimer(timer);
166
+ return {
167
+ signal: controller.signal,
168
+ cleanup: () => {
169
+ clearTimeout(timer);
170
+ external?.removeEventListener("abort", onAbort);
171
+ }
172
+ };
173
+ }
174
+ var PREFIX = "[hobots]";
89
175
  function makeLogger(enabled, prefix = PREFIX) {
90
176
  return {
91
177
  debug: (...args) => enabled && console.debug(colorize(prefix, "gray"), ...args),
@@ -98,7 +184,8 @@ var ANSI = {
98
184
  gray: "\x1B[90m",
99
185
  cyan: "\x1B[36m",
100
186
  yellow: "\x1B[33m",
101
- red: "\x1B[31m"
187
+ red: "\x1B[31m",
188
+ green: "\x1B[32m"
102
189
  };
103
190
  function colorEnabled() {
104
191
  if (!isNode()) return false;
@@ -125,11 +212,11 @@ function eventFromException(error, level = "error") {
125
212
  let frames = [];
126
213
  if (error instanceof Error) {
127
214
  type = error.name || "Error";
128
- value = error.message;
215
+ value = scrubSecrets(error.message);
129
216
  frames = parseStacktrace(error.stack);
130
217
  } else {
131
218
  type = "NonError";
132
- value = safeString(error);
219
+ value = scrubSecrets(safeString(error));
133
220
  }
134
221
  return {
135
222
  event_id: uuid4(),
@@ -147,12 +234,13 @@ function eventFromMessage(message, level = "info") {
147
234
  event_id: uuid4(),
148
235
  timestamp: nowISO(),
149
236
  level,
150
- message
237
+ message: scrubSecrets(message)
151
238
  };
152
239
  }
153
240
 
154
241
  // src/transport.ts
155
242
  var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
243
+ var REQUEST_TIMEOUT_MS = 1e4;
156
244
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
157
245
  var DEFAULT_MAX_BATCH_SIZE = 10;
158
246
  var DEFAULT_MAX_QUEUE_SIZE = 200;
@@ -219,11 +307,13 @@ var Transport = class {
219
307
  }
220
308
  async sendWithRetry(envelope) {
221
309
  for (let attempt = 0; ; attempt++) {
310
+ let serverDelay;
222
311
  try {
223
312
  const response = await fetch(this.endpoint, {
224
313
  method: "POST",
225
314
  headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
226
- body: JSON.stringify(envelope)
315
+ body: JSON.stringify(envelope),
316
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
227
317
  });
228
318
  if (response.ok) {
229
319
  this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
@@ -233,13 +323,15 @@ var Transport = class {
233
323
  this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
234
324
  return;
235
325
  }
326
+ if (response.status === 429) serverDelay = retryAfterMs(response);
236
327
  throw new Error(`HTTP ${response.status}`);
237
328
  } catch (error) {
238
- const delay = RETRY_DELAYS_MS[attempt];
239
- if (delay === void 0) {
329
+ const base = RETRY_DELAYS_MS[attempt];
330
+ if (base === void 0) {
240
331
  this.logger.error("envelope descartado ap\xF3s retries:", error);
241
332
  return;
242
333
  }
334
+ const delay = serverDelay ?? jitter(base);
243
335
  this.logger.warn(`falha no envio (${String(error)}), retry em ${delay}ms`);
244
336
  await sleep(delay);
245
337
  }
@@ -269,22 +361,38 @@ function runtimeContexts() {
269
361
  return { runtime: { name: "browser" } };
270
362
  }
271
363
  var Client = class {
272
- options;
273
- config;
274
364
  logger;
365
+ // campos privados + getters de protótipo: o clientSecret não aparece em
366
+ // JSON.stringify/serialização enumerável do Client (ex.: um objeto capturado
367
+ // que referencie o client), mas client.options/config seguem funcionando
368
+ #options;
369
+ #config;
275
370
  transport;
276
371
  constructor(options) {
277
- this.options = options;
372
+ this.#options = options;
278
373
  this.logger = makeLogger(!!options.debug);
279
- this.config = resolveConfig(options);
374
+ this.#config = resolveConfig(options);
280
375
  this.transport = new Transport({
281
- endpoint: this.config.ingestEndpoint,
282
- key: this.config.key,
376
+ endpoint: this.#config.ingestEndpoint,
377
+ key: this.#config.key,
283
378
  logger: this.logger,
284
379
  flushIntervalMs: options.flushIntervalMs,
285
380
  maxBatchSize: options.maxBatchSize
286
381
  });
287
382
  }
383
+ get options() {
384
+ return this.#options;
385
+ }
386
+ get config() {
387
+ return this.#config;
388
+ }
389
+ /** Serialização explícita nunca inclui o secret. */
390
+ toJSON() {
391
+ return { sdk: SDK_NAME, version: SDK_VERSION, baseUrl: this.#config.baseUrl };
392
+ }
393
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
394
+ return `Client { baseUrl: '${this.#config.baseUrl}' }`;
395
+ }
288
396
  captureException(error, hint, scope2) {
289
397
  return this.captureEvent(eventFromException(error, hint?.level ?? "error"), hint, scope2);
290
398
  }
@@ -538,6 +646,15 @@ var Scope = class _Scope {
538
646
  function spanId() {
539
647
  return uuid4().slice(0, 16);
540
648
  }
649
+ function printSpanLog(kind, phase, label, startTs, endTs) {
650
+ const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
651
+ const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? "green" : "cyan");
652
+ const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
653
+ console.log(`${tag} ${label}${suffix}`);
654
+ }
655
+ function spanLabel(op, description) {
656
+ return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
657
+ }
541
658
  var Span = class {
542
659
  constructor(transaction, parentSpanId, op, description) {
543
660
  this.transaction = transaction;
@@ -563,7 +680,9 @@ var Span = class {
563
680
  return this.transaction.createSpan(this.spanId, op, description);
564
681
  }
565
682
  finish() {
566
- if (!this.endTimestamp) this.endTimestamp = nowISO();
683
+ if (this.endTimestamp) return;
684
+ this.endTimestamp = nowISO();
685
+ printSpanLog("span", "finish", spanLabel(this.op, this.description), this.startTimestamp, this.endTimestamp);
567
686
  }
568
687
  toJSON() {
569
688
  return {
@@ -585,6 +704,7 @@ var Transaction = class {
585
704
  this.op = op;
586
705
  this.sampled = sampled;
587
706
  this.runId = runId;
707
+ printSpanLog("transaction", "start", op ? `${name} (${op})` : name);
588
708
  }
589
709
  eventId = uuid4();
590
710
  spanId = spanId();
@@ -596,6 +716,7 @@ var Transaction = class {
596
716
  createSpan(parentSpanId, op, description) {
597
717
  const span = new Span(this, parentSpanId, op, description);
598
718
  this.spans.push(span);
719
+ printSpanLog("span", "start", spanLabel(op, description));
599
720
  return span;
600
721
  }
601
722
  /** Span direto da transaction. */
@@ -618,6 +739,14 @@ var Transaction = class {
618
739
  if (this.finished) return;
619
740
  this.finished = true;
620
741
  for (const span of this.spans) span.finish();
742
+ const endTimestamp = nowISO();
743
+ printSpanLog(
744
+ "transaction",
745
+ "finish",
746
+ this.op ? `${this.name} (${this.op})` : this.name,
747
+ this.startTimestamp,
748
+ endTimestamp
749
+ );
621
750
  if (!this.sampled || !this.client) return;
622
751
  const payload = {
623
752
  event_id: this.eventId,
@@ -625,7 +754,7 @@ var Transaction = class {
625
754
  op: this.op,
626
755
  status: this.status,
627
756
  start_timestamp: this.startTimestamp,
628
- timestamp: nowISO(),
757
+ timestamp: endTimestamp,
629
758
  spans: this.spans.map((span) => span.toJSON()),
630
759
  tags: Object.keys(this.tags).length > 0 ? this.tags : void 0,
631
760
  run_id: this.runId
@@ -682,6 +811,8 @@ async function withMonitorImpl(client2, captureError, monitorSlug, fn, options)
682
811
 
683
812
  // src/manager/api.ts
684
813
  var RETRY_DELAYS_MS2 = [1e3, 4e3, 16e3];
814
+ var POLL_TIMEOUT_MS = 3e4;
815
+ var UPDATE_TIMEOUT_MS = 1e4;
685
816
  var ManagerApiError = class extends Error {
686
817
  constructor(message, status) {
687
818
  super(message);
@@ -706,16 +837,39 @@ var ManagerApi = class {
706
837
  }
707
838
  /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
708
839
  async poll(request, signal) {
709
- const response = await fetch(`${this.baseUrl}/poll`, {
710
- method: "POST",
711
- headers: this.headers(),
712
- body: JSON.stringify(request),
713
- signal
714
- });
840
+ const { signal: timeoutSignal, cleanup } = withTimeoutSignal(POLL_TIMEOUT_MS, signal);
841
+ let response;
842
+ try {
843
+ response = await fetch(`${this.baseUrl}/poll`, {
844
+ method: "POST",
845
+ headers: this.headers(),
846
+ body: JSON.stringify(request),
847
+ signal: timeoutSignal
848
+ });
849
+ } finally {
850
+ cleanup();
851
+ }
715
852
  if (!response.ok) {
716
853
  throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
717
854
  }
718
- return await response.json();
855
+ return this.parsePollResponse(await response.json(), request.max ?? 1);
856
+ }
857
+ /** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
858
+ parsePollResponse(body, max) {
859
+ const runs = body?.runs;
860
+ if (!Array.isArray(runs)) {
861
+ throw new ManagerApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
862
+ }
863
+ const valid = [];
864
+ for (const run of runs.slice(0, Math.max(0, max))) {
865
+ if (typeof run?.id !== "string" || typeof run.task !== "string") continue;
866
+ const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
867
+ valid.push({ ...run, id: run.id, task: run.task, params });
868
+ }
869
+ if (valid.length < runs.length) {
870
+ this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
871
+ }
872
+ return { runs: valid };
719
873
  }
720
874
  /**
721
875
  * PATCH de status/logs com retry em falha de rede/5xx/429.
@@ -724,25 +878,29 @@ var ManagerApi = class {
724
878
  async updateRun(runId, update) {
725
879
  for (let attempt = 0; ; attempt++) {
726
880
  let retriable;
881
+ let serverDelay;
727
882
  try {
728
883
  const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
729
884
  method: "PATCH",
730
885
  headers: this.headers(),
731
- body: JSON.stringify(update)
886
+ body: JSON.stringify(update),
887
+ signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
732
888
  });
733
889
  if (response.ok) return;
734
890
  if (response.status >= 400 && response.status < 500 && response.status !== 429) {
735
891
  throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
736
892
  }
893
+ if (response.status === 429) serverDelay = retryAfterMs(response);
737
894
  retriable = new Error(`HTTP ${response.status}`);
738
895
  } catch (error) {
739
896
  if (error instanceof ManagerApiError) throw error;
740
897
  retriable = error;
741
898
  }
742
- const delay = RETRY_DELAYS_MS2[attempt];
743
- if (delay === void 0) {
899
+ const base = RETRY_DELAYS_MS2[attempt];
900
+ if (base === void 0) {
744
901
  throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
745
902
  }
903
+ const delay = serverDelay ?? jitter(base);
746
904
  this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
747
905
  await sleep(delay);
748
906
  }
@@ -767,11 +925,12 @@ var LOG_LEVEL_COLOR = {
767
925
  debug: "gray",
768
926
  info: "cyan",
769
927
  warning: "yellow",
770
- error: "red"
928
+ error: "red",
929
+ success: "green"
771
930
  };
772
931
  function printRunLog(task, level, message) {
773
932
  const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
774
- const method = level === "warning" ? "warn" : level;
933
+ const method = level === "warning" ? "warn" : level === "success" ? "info" : level;
775
934
  console[method](line, message);
776
935
  }
777
936
  function runCanceledError() {
@@ -816,7 +975,8 @@ var RunReporter = class {
816
975
  debug: (message) => this.log("debug", message),
817
976
  info: (message) => this.log("info", message),
818
977
  warn: (message) => this.log("warning", message),
819
- error: (message) => this.log("error", message)
978
+ error: (message) => this.log("error", message),
979
+ success: (message) => this.log("success", message)
820
980
  };
821
981
  const signal = this.abortController.signal;
822
982
  this.context = {
@@ -864,13 +1024,14 @@ var RunReporter = class {
864
1024
  }
865
1025
  log(level, message) {
866
1026
  if (this.dead) return;
867
- printRunLog(this.context.task, level, message);
1027
+ const scrubbed = scrubSecrets(message);
1028
+ printRunLog(this.context.task, level, scrubbed);
868
1029
  if (this.loggedCount >= MAX_LOGS_PER_RUN) {
869
1030
  this.droppedCount++;
870
1031
  return;
871
1032
  }
872
1033
  this.loggedCount++;
873
- const truncated = message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : message;
1034
+ const truncated = scrubbed.length > MAX_LOG_MESSAGE_CHARS ? `${scrubbed.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : scrubbed;
874
1035
  this.buffer.push({ ts: nowISO(), level, message: truncated });
875
1036
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
876
1037
  this.flushLogs();
@@ -1054,8 +1215,8 @@ var ManagerAgent = class {
1054
1215
  constructor(options, registry2) {
1055
1216
  this.options = options;
1056
1217
  this.registry = registry2;
1057
- this.logger = makeLogger(options.debug ?? false, "[hobots-manager]");
1058
- const config = resolveConfig(options, "[hobots-manager]");
1218
+ this.logger = makeLogger(options.debug ?? false, "[hobots]");
1219
+ const config = resolveConfig(options, "[hobots]");
1059
1220
  this.api = new ManagerApi({ baseUrl: config.managerBaseUrl, key: config.key, logger: this.logger });
1060
1221
  this.client = options.client;
1061
1222
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
@@ -1089,10 +1250,10 @@ var ManagerAgent = class {
1089
1250
  start() {
1090
1251
  if (this.state === "started") return;
1091
1252
  if (this.state === "stopping") {
1092
- throw new Error("[hobots-manager] start() chamado durante stop() \u2014 aguarde o stop terminar");
1253
+ throw new Error("[hobots] start() chamado durante stop() \u2014 aguarde o stop terminar");
1093
1254
  }
1094
1255
  if (this.registry.size === 0) {
1095
- throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
1256
+ throw new Error("[hobots] nenhuma task registrada \u2014 chame register() antes de start()");
1096
1257
  }
1097
1258
  this.state = "started";
1098
1259
  if (this.options.installExitHandlers !== false && !this.uninstallShutdown) {
@@ -1174,6 +1335,7 @@ var ManagerAgent = class {
1174
1335
  this.consecutiveFailures = 0;
1175
1336
  this.consecutiveAuthFailures = 0;
1176
1337
  for (const run of response.runs) {
1338
+ if (this.activeRuns.size >= this.concurrency) break;
1177
1339
  this.dispatch(run);
1178
1340
  }
1179
1341
  this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);
@@ -1194,7 +1356,7 @@ var ManagerAgent = class {
1194
1356
  }
1195
1357
  this.consecutiveFailures++;
1196
1358
  const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)];
1197
- const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16e3));
1359
+ const delay = Math.min(Math.max(jitter(ladder), 0), Math.max(this.pollIntervalMs, 16e3));
1198
1360
  this.logger.warn(`poll falhou (${String(error)}), pr\xF3xima tentativa em ${delay}ms`);
1199
1361
  return delay;
1200
1362
  }
@@ -1206,7 +1368,7 @@ var ManagerAgent = class {
1206
1368
  this.activeRuns.add(promise);
1207
1369
  }
1208
1370
  async executeRun(run) {
1209
- const tx = new Transaction(this.client, `run:${run.task}`, "manager.run", true, run.id);
1371
+ const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", true, run.id);
1210
1372
  const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx, this.logFlushIntervalMs, this.cancelCheckIntervalMs);
1211
1373
  this.reporters.add(reporter);
1212
1374
  try {
@@ -1272,11 +1434,11 @@ function toRunError(error) {
1272
1434
  if (error instanceof Error) {
1273
1435
  return {
1274
1436
  type: error.name,
1275
- message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),
1276
- stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : void 0
1437
+ message: scrubSecrets(truncate(error.message, MAX_ERROR_MESSAGE_CHARS)),
1438
+ stack: error.stack ? scrubSecrets(truncate(error.stack, MAX_ERROR_STACK_CHARS)) : void 0
1277
1439
  };
1278
1440
  }
1279
- return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };
1441
+ return { message: scrubSecrets(truncate(String(error), MAX_ERROR_MESSAGE_CHARS)) };
1280
1442
  }
1281
1443
 
1282
1444
  // src/core.ts
@@ -1420,6 +1582,7 @@ export {
1420
1582
  init,
1421
1583
  register,
1422
1584
  resolveConfig,
1585
+ scrubSecrets,
1423
1586
  setContext,
1424
1587
  setExtra,
1425
1588
  setTag,