hobots 1.5.1 → 1.7.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.js CHANGED
@@ -1,25 +1,51 @@
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,
21
47
  ingestEndpoint: `${baseUrl}/v1/ingest`,
22
- managerBaseUrl: `${baseUrl}/v1/manager`
48
+ tasksBaseUrl: `${baseUrl}/v1/tasks`
23
49
  };
24
50
  }
25
51
 
@@ -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 ? "2.0.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,69 @@ 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
+ function truncate(value, max) {
143
+ return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
144
+ }
145
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
146
+ async function fetchWithRetry(send, options) {
147
+ const delays = options.delays ?? DEFAULT_RETRY_DELAYS_MS;
148
+ for (let attempt = 0; ; attempt++) {
149
+ let retriable;
150
+ let serverDelay;
151
+ try {
152
+ const response = await send();
153
+ if (response.ok) return response;
154
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) return response;
155
+ if (response.status === 429) serverDelay = retryAfterMs(response);
156
+ retriable = new Error(`HTTP ${response.status}`);
157
+ } catch (error) {
158
+ retriable = error;
159
+ }
160
+ const base = delays[attempt];
161
+ if (base === void 0) {
162
+ throw retriable instanceof Error ? retriable : new Error(String(retriable));
163
+ }
164
+ const delay = serverDelay ?? jitter(base);
165
+ options.logger.warn(`${options.label}: falha (${String(retriable)}), retry em ${delay}ms`);
166
+ await sleep(delay);
167
+ }
168
+ }
169
+ var MAX_RETRY_AFTER_MS = 6e4;
170
+ function retryAfterMs(response) {
171
+ const header = response.headers.get("retry-after");
172
+ if (!header) return void 0;
173
+ const seconds = Number(header);
174
+ if (Number.isFinite(seconds)) {
175
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, seconds * 1e3));
176
+ }
177
+ const date = Date.parse(header);
178
+ if (Number.isNaN(date)) return void 0;
179
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, date - Date.now()));
180
+ }
181
+ function withTimeoutSignal(timeoutMs, external) {
182
+ const controller = new AbortController();
183
+ const onAbort = () => controller.abort(external?.reason);
184
+ if (external?.aborted) {
185
+ onAbort();
186
+ } else {
187
+ external?.addEventListener("abort", onAbort, { once: true });
188
+ }
189
+ const timer = setTimeout(() => {
190
+ controller.abort(Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms`), { name: "TimeoutError" }));
191
+ }, timeoutMs);
192
+ unrefTimer(timer);
193
+ return {
194
+ signal: controller.signal,
195
+ cleanup: () => {
196
+ clearTimeout(timer);
197
+ external?.removeEventListener("abort", onAbort);
198
+ }
199
+ };
200
+ }
201
+ var PREFIX = "[hobots]";
89
202
  function makeLogger(enabled, prefix = PREFIX) {
90
203
  return {
91
204
  debug: (...args) => enabled && console.debug(colorize(prefix, "gray"), ...args),
@@ -126,11 +239,11 @@ function eventFromException(error, level = "error") {
126
239
  let frames = [];
127
240
  if (error instanceof Error) {
128
241
  type = error.name || "Error";
129
- value = error.message;
242
+ value = scrubSecrets(error.message);
130
243
  frames = parseStacktrace(error.stack);
131
244
  } else {
132
245
  type = "NonError";
133
- value = safeString(error);
246
+ value = scrubSecrets(safeString(error));
134
247
  }
135
248
  return {
136
249
  event_id: uuid4(),
@@ -148,12 +261,12 @@ function eventFromMessage(message, level = "info") {
148
261
  event_id: uuid4(),
149
262
  timestamp: nowISO(),
150
263
  level,
151
- message
264
+ message: scrubSecrets(message)
152
265
  };
153
266
  }
154
267
 
155
268
  // src/transport.ts
156
- var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
269
+ var REQUEST_TIMEOUT_MS = 1e4;
157
270
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
158
271
  var DEFAULT_MAX_BATCH_SIZE = 10;
159
272
  var DEFAULT_MAX_QUEUE_SIZE = 200;
@@ -219,32 +332,26 @@ var Transport = class {
219
332
  return request;
220
333
  }
221
334
  async sendWithRetry(envelope) {
222
- for (let attempt = 0; ; attempt++) {
223
- try {
224
- const response = await fetch(this.endpoint, {
335
+ let response;
336
+ try {
337
+ response = await fetchWithRetry(
338
+ () => fetch(this.endpoint, {
225
339
  method: "POST",
226
340
  headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
227
- body: JSON.stringify(envelope)
228
- });
229
- if (response.ok) {
230
- this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
231
- return;
232
- }
233
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
234
- this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
235
- return;
236
- }
237
- throw new Error(`HTTP ${response.status}`);
238
- } catch (error) {
239
- const delay = RETRY_DELAYS_MS[attempt];
240
- if (delay === void 0) {
241
- this.logger.error("envelope descartado ap\xF3s retries:", error);
242
- return;
243
- }
244
- this.logger.warn(`falha no envio (${String(error)}), retry em ${delay}ms`);
245
- await sleep(delay);
246
- }
341
+ body: JSON.stringify(envelope),
342
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
343
+ }),
344
+ { logger: this.logger, label: "envio de envelope" }
345
+ );
346
+ } catch (error) {
347
+ this.logger.error("envelope descartado ap\xF3s retries:", error);
348
+ return;
247
349
  }
350
+ if (response.ok) {
351
+ this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
352
+ return;
353
+ }
354
+ this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
248
355
  }
249
356
  /** Envia o que estiver na fila e aguarda envios em andamento (até timeoutMs). */
250
357
  async flush(timeoutMs = 5e3) {
@@ -270,22 +377,38 @@ function runtimeContexts() {
270
377
  return { runtime: { name: "browser" } };
271
378
  }
272
379
  var Client = class {
273
- options;
274
- config;
275
380
  logger;
381
+ // campos privados + getters de protótipo: o clientSecret não aparece em
382
+ // JSON.stringify/serialização enumerável do Client (ex.: um objeto capturado
383
+ // que referencie o client), mas client.options/config seguem funcionando
384
+ #options;
385
+ #config;
276
386
  transport;
277
- constructor(options) {
278
- this.options = options;
387
+ constructor(options, config = resolveConfig(options)) {
388
+ this.#options = options;
279
389
  this.logger = makeLogger(!!options.debug);
280
- this.config = resolveConfig(options);
390
+ this.#config = config;
281
391
  this.transport = new Transport({
282
- endpoint: this.config.ingestEndpoint,
283
- key: this.config.key,
392
+ endpoint: this.#config.ingestEndpoint,
393
+ key: this.#config.key,
284
394
  logger: this.logger,
285
395
  flushIntervalMs: options.flushIntervalMs,
286
396
  maxBatchSize: options.maxBatchSize
287
397
  });
288
398
  }
399
+ get options() {
400
+ return this.#options;
401
+ }
402
+ get config() {
403
+ return this.#config;
404
+ }
405
+ /** Serialização explícita nunca inclui o secret. */
406
+ toJSON() {
407
+ return { sdk: SDK_NAME, version: SDK_VERSION, baseUrl: this.#config.baseUrl };
408
+ }
409
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
410
+ return `Client { baseUrl: '${this.#config.baseUrl}' }`;
411
+ }
289
412
  captureException(error, hint, scope2) {
290
413
  return this.captureEvent(eventFromException(error, hint?.level ?? "error"), hint, scope2);
291
414
  }
@@ -670,48 +793,31 @@ function sendCheckIn(client2, options) {
670
793
  return checkInId;
671
794
  }
672
795
  async function withMonitorImpl(client2, captureError, monitorSlug, fn, options) {
673
- const checkInId = sendCheckIn(client2, {
674
- monitorSlug,
675
- status: "in_progress",
676
- schedule: options?.schedule,
677
- timezone: options?.timezone
678
- });
796
+ const base = { monitorSlug, schedule: options?.schedule, timezone: options?.timezone };
797
+ const checkInId = sendCheckIn(client2, { ...base, status: "in_progress" });
679
798
  const startedAt = Date.now();
680
799
  try {
681
800
  const result = await fn();
682
- sendCheckIn(client2, {
683
- monitorSlug,
684
- status: "ok",
685
- checkInId,
686
- durationMs: Date.now() - startedAt,
687
- schedule: options?.schedule,
688
- timezone: options?.timezone
689
- });
801
+ sendCheckIn(client2, { ...base, status: "ok", checkInId, durationMs: Date.now() - startedAt });
690
802
  return result;
691
803
  } catch (error) {
692
- sendCheckIn(client2, {
693
- monitorSlug,
694
- status: "error",
695
- checkInId,
696
- durationMs: Date.now() - startedAt,
697
- schedule: options?.schedule,
698
- timezone: options?.timezone
699
- });
804
+ sendCheckIn(client2, { ...base, status: "error", checkInId, durationMs: Date.now() - startedAt });
700
805
  captureError(error);
701
806
  throw error;
702
807
  }
703
808
  }
704
809
 
705
- // src/manager/api.ts
706
- var RETRY_DELAYS_MS2 = [1e3, 4e3, 16e3];
707
- var ManagerApiError = class extends Error {
810
+ // src/tasks/api.ts
811
+ var POLL_TIMEOUT_MS = 3e4;
812
+ var UPDATE_TIMEOUT_MS = 1e4;
813
+ var TasksApiError = class extends Error {
708
814
  constructor(message, status) {
709
815
  super(message);
710
816
  this.status = status;
711
- this.name = "ManagerApiError";
817
+ this.name = "TasksApiError";
712
818
  }
713
819
  };
714
- var ManagerApi = class {
820
+ var TasksApi = class {
715
821
  baseUrl;
716
822
  key;
717
823
  logger;
@@ -728,45 +834,61 @@ var ManagerApi = class {
728
834
  }
729
835
  /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
730
836
  async poll(request, signal) {
731
- const response = await fetch(`${this.baseUrl}/poll`, {
732
- method: "POST",
733
- headers: this.headers(),
734
- body: JSON.stringify(request),
735
- signal
736
- });
837
+ const { signal: timeoutSignal, cleanup } = withTimeoutSignal(POLL_TIMEOUT_MS, signal);
838
+ let response;
839
+ try {
840
+ response = await fetch(`${this.baseUrl}/poll`, {
841
+ method: "POST",
842
+ headers: this.headers(),
843
+ body: JSON.stringify(request),
844
+ signal: timeoutSignal
845
+ });
846
+ } finally {
847
+ cleanup();
848
+ }
737
849
  if (!response.ok) {
738
- throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
850
+ throw new TasksApiError(`poll falhou (HTTP ${response.status})`, response.status);
851
+ }
852
+ return this.parsePollResponse(await response.json(), request.max ?? 1);
853
+ }
854
+ /** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
855
+ parsePollResponse(body, max) {
856
+ const runs = body?.runs;
857
+ if (!Array.isArray(runs)) {
858
+ throw new TasksApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
859
+ }
860
+ const valid = [];
861
+ for (const run of runs.slice(0, Math.max(0, max))) {
862
+ if (typeof run?.id !== "string" || typeof run.task !== "string") continue;
863
+ const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
864
+ valid.push({ ...run, id: run.id, task: run.task, params });
865
+ }
866
+ if (valid.length < runs.length) {
867
+ this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
739
868
  }
740
- return await response.json();
869
+ return { runs: valid };
741
870
  }
742
871
  /**
743
872
  * PATCH de status/logs com retry em falha de rede/5xx/429.
744
- * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
873
+ * 4xx é definitivo e vira TasksApiError imediato (409 = run cancelada/transição inválida).
745
874
  */
746
875
  async updateRun(runId, update) {
747
- for (let attempt = 0; ; attempt++) {
748
- let retriable;
749
- try {
750
- const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
876
+ let response;
877
+ try {
878
+ response = await fetchWithRetry(
879
+ () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
751
880
  method: "PATCH",
752
881
  headers: this.headers(),
753
- body: JSON.stringify(update)
754
- });
755
- if (response.ok) return;
756
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
757
- throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
758
- }
759
- retriable = new Error(`HTTP ${response.status}`);
760
- } catch (error) {
761
- if (error instanceof ManagerApiError) throw error;
762
- retriable = error;
763
- }
764
- const delay = RETRY_DELAYS_MS2[attempt];
765
- if (delay === void 0) {
766
- throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
767
- }
768
- this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
769
- await sleep(delay);
882
+ body: JSON.stringify(update),
883
+ signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
884
+ }),
885
+ { logger: this.logger, label: `update da run ${runId}` }
886
+ );
887
+ } catch (error) {
888
+ throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
889
+ }
890
+ if (!response.ok) {
891
+ throw new TasksApiError(`update rejeitado (HTTP ${response.status})`, response.status);
770
892
  }
771
893
  }
772
894
  /**
@@ -784,7 +906,7 @@ var ManagerApi = class {
784
906
  }
785
907
  };
786
908
 
787
- // src/manager/context.ts
909
+ // src/tasks/context.ts
788
910
  var LOG_LEVEL_COLOR = {
789
911
  debug: "gray",
790
912
  info: "cyan",
@@ -888,14 +1010,14 @@ var RunReporter = class {
888
1010
  }
889
1011
  log(level, message) {
890
1012
  if (this.dead) return;
891
- printRunLog(this.context.task, level, message);
1013
+ const scrubbed = scrubSecrets(message);
1014
+ printRunLog(this.context.task, level, scrubbed);
892
1015
  if (this.loggedCount >= MAX_LOGS_PER_RUN) {
893
1016
  this.droppedCount++;
894
1017
  return;
895
1018
  }
896
1019
  this.loggedCount++;
897
- const truncated = message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : message;
898
- this.buffer.push({ ts: nowISO(), level, message: truncated });
1020
+ this.buffer.push({ ts: nowISO(), level, message: truncate(scrubbed, MAX_LOG_MESSAGE_CHARS) });
899
1021
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
900
1022
  this.flushLogs();
901
1023
  } else {
@@ -986,7 +1108,7 @@ var RunReporter = class {
986
1108
  try {
987
1109
  await this.api.updateRun(this.runId, payload);
988
1110
  } catch (error) {
989
- if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
1111
+ if (error instanceof TasksApiError && (error.status === 409 || error.status === 404)) {
990
1112
  this.dead = true;
991
1113
  this.stopCancelWatch();
992
1114
  this.abort();
@@ -1002,7 +1124,7 @@ var RunReporter = class {
1002
1124
  }
1003
1125
  };
1004
1126
 
1005
- // src/manager/run-context-store.ts
1127
+ // src/tasks/run-context-store.ts
1006
1128
  var storage;
1007
1129
  var initPromise;
1008
1130
  async function ensureStorage() {
@@ -1012,15 +1134,15 @@ async function ensureStorage() {
1012
1134
  });
1013
1135
  await initPromise;
1014
1136
  }
1015
- async function runWithRunId(runId, fn) {
1137
+ async function runWithRunContext(store, fn) {
1016
1138
  await ensureStorage();
1017
- return storage ? storage.run({ runId }, fn) : fn();
1139
+ return storage ? storage.run(store, fn) : fn();
1018
1140
  }
1019
- function currentRunId() {
1020
- return storage?.getStore()?.runId;
1141
+ function currentRunTransaction() {
1142
+ return storage?.getStore()?.tx;
1021
1143
  }
1022
1144
 
1023
- // src/manager/shutdown.ts
1145
+ // src/tasks/shutdown.ts
1024
1146
  var WATCHED_SIGNALS = ["SIGINT", "SIGTERM"];
1025
1147
  function installShutdownHandlers(agent2) {
1026
1148
  if (!isNode()) return () => {
@@ -1064,7 +1186,7 @@ function installShutdownHandlers(agent2) {
1064
1186
  };
1065
1187
  }
1066
1188
 
1067
- // src/manager/agent.ts
1189
+ // src/tasks/agent.ts
1068
1190
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
1069
1191
  var MIN_POLL_INTERVAL_MS = 1e3;
1070
1192
  var MAX_CONCURRENCY = 5;
@@ -1074,13 +1196,13 @@ function hostname() {
1074
1196
  if (!isNode()) return void 0;
1075
1197
  return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
1076
1198
  }
1077
- var ManagerAgent = class {
1199
+ var TaskAgent = class {
1078
1200
  constructor(options, registry2) {
1079
1201
  this.options = options;
1080
1202
  this.registry = registry2;
1081
- this.logger = makeLogger(options.debug ?? false, "[hobots-manager]");
1082
- const config = resolveConfig(options, "[hobots-manager]");
1083
- this.api = new ManagerApi({ baseUrl: config.managerBaseUrl, key: config.key, logger: this.logger });
1203
+ this.logger = makeLogger(options.debug ?? false);
1204
+ const config = options.config;
1205
+ this.api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger: this.logger });
1084
1206
  this.client = options.client;
1085
1207
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
1086
1208
  this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));
@@ -1088,7 +1210,7 @@ var ManagerAgent = class {
1088
1210
  this.taskTimeoutMs = options.taskTimeoutMs;
1089
1211
  this.logFlushIntervalMs = options.logFlushIntervalMs;
1090
1212
  this.cancelCheckIntervalMs = options.cancelCheckIntervalMs;
1091
- this.logger.debug("agente configurado", { baseUrl: config.managerBaseUrl });
1213
+ this.logger.debug("agente configurado", { baseUrl: config.tasksBaseUrl });
1092
1214
  }
1093
1215
  logger;
1094
1216
  api;
@@ -1113,10 +1235,10 @@ var ManagerAgent = class {
1113
1235
  start() {
1114
1236
  if (this.state === "started") return;
1115
1237
  if (this.state === "stopping") {
1116
- throw new Error("[hobots-manager] start() chamado durante stop() \u2014 aguarde o stop terminar");
1238
+ throw new Error("[hobots] start() chamado durante stop() \u2014 aguarde o stop terminar");
1117
1239
  }
1118
1240
  if (this.registry.size === 0) {
1119
- throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
1241
+ throw new Error("[hobots] nenhuma task registrada \u2014 chame register() antes de start()");
1120
1242
  }
1121
1243
  this.state = "started";
1122
1244
  if (this.options.installExitHandlers !== false && !this.uninstallShutdown) {
@@ -1198,6 +1320,7 @@ var ManagerAgent = class {
1198
1320
  this.consecutiveFailures = 0;
1199
1321
  this.consecutiveAuthFailures = 0;
1200
1322
  for (const run of response.runs) {
1323
+ if (this.activeRuns.size >= this.concurrency) break;
1201
1324
  this.dispatch(run);
1202
1325
  }
1203
1326
  this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);
@@ -1209,7 +1332,7 @@ var ManagerAgent = class {
1209
1332
  }
1210
1333
  }
1211
1334
  delayAfterPollError(error) {
1212
- if (error instanceof ManagerApiError && error.status === 401) {
1335
+ if (error instanceof TasksApiError && error.status === 401) {
1213
1336
  this.consecutiveAuthFailures++;
1214
1337
  if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
1215
1338
  this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos");
@@ -1218,7 +1341,7 @@ var ManagerAgent = class {
1218
1341
  }
1219
1342
  this.consecutiveFailures++;
1220
1343
  const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)];
1221
- const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16e3));
1344
+ const delay = Math.min(Math.max(jitter(ladder), 0), Math.max(this.pollIntervalMs, 16e3));
1222
1345
  this.logger.warn(`poll falhou (${String(error)}), pr\xF3xima tentativa em ${delay}ms`);
1223
1346
  return delay;
1224
1347
  }
@@ -1230,7 +1353,7 @@ var ManagerAgent = class {
1230
1353
  this.activeRuns.add(promise);
1231
1354
  }
1232
1355
  async executeRun(run) {
1233
- const tx = new Transaction(this.client, `run:${run.task}`, "manager.run", true, run.id);
1356
+ const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", true, run.id);
1234
1357
  const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx, this.logFlushIntervalMs, this.cancelCheckIntervalMs);
1235
1358
  this.reporters.add(reporter);
1236
1359
  try {
@@ -1254,7 +1377,7 @@ var ManagerAgent = class {
1254
1377
  }
1255
1378
  this.logger.debug(`executando run ${run.id} (${run.task})`);
1256
1379
  try {
1257
- await runWithRunId(run.id, () => this.invokeWithTimeout(handler, run, reporter));
1380
+ await runWithRunContext({ runId: run.id, tx }, () => this.invokeWithTimeout(handler, run, reporter));
1258
1381
  reporter.reportSuccess();
1259
1382
  tx.setStatus("ok");
1260
1383
  this.logger.debug(`run ${run.id} conclu\xEDda`);
@@ -1289,18 +1412,15 @@ var ManagerAgent = class {
1289
1412
  var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
1290
1413
  var MAX_ERROR_MESSAGE_CHARS = 1e4;
1291
1414
  var MAX_ERROR_STACK_CHARS = 2e4;
1292
- function truncate(value, max) {
1293
- return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
1294
- }
1295
1415
  function toRunError(error) {
1296
1416
  if (error instanceof Error) {
1297
1417
  return {
1298
1418
  type: error.name,
1299
- message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),
1300
- stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : void 0
1419
+ message: scrubSecrets(truncate(error.message, MAX_ERROR_MESSAGE_CHARS)),
1420
+ stack: error.stack ? scrubSecrets(truncate(error.stack, MAX_ERROR_STACK_CHARS)) : void 0
1301
1421
  };
1302
1422
  }
1303
- return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };
1423
+ return { message: scrubSecrets(truncate(String(error), MAX_ERROR_MESSAGE_CHARS)) };
1304
1424
  }
1305
1425
 
1306
1426
  // src/core.ts
@@ -1310,7 +1430,7 @@ var scope = new Scope();
1310
1430
  var heartbeat;
1311
1431
  var agent;
1312
1432
  var registry = /* @__PURE__ */ new Map();
1313
- var warnedManagerDisabled = false;
1433
+ var warnedTasksDisabled = false;
1314
1434
  function init(options) {
1315
1435
  if (client) {
1316
1436
  client.logger.warn("init() chamado mais de uma vez \u2014 substituindo o client anterior");
@@ -1321,8 +1441,9 @@ function init(options) {
1321
1441
  void agent.stop(0);
1322
1442
  agent = void 0;
1323
1443
  }
1324
- warnedManagerDisabled = false;
1325
- client = new Client(options);
1444
+ warnedTasksDisabled = false;
1445
+ const config = resolveConfig(options);
1446
+ client = new Client(options, config);
1326
1447
  if (options.captureUnhandled !== false) {
1327
1448
  installGlobalHandlers(client, () => scope);
1328
1449
  }
@@ -1331,10 +1452,10 @@ function init(options) {
1331
1452
  heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId);
1332
1453
  heartbeat.start();
1333
1454
  }
1334
- if (options.manager) {
1335
- const managerOptions = options.manager === true ? {} : options.manager;
1336
- agent = new ManagerAgent(
1337
- { ...managerOptions, clientSecret: options.clientSecret, instanceId: options.instanceId, debug: options.debug, client },
1455
+ if (options.tasks) {
1456
+ const tasksOptions = options.tasks === true ? {} : options.tasks;
1457
+ agent = new TaskAgent(
1458
+ { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client },
1338
1459
  registry
1339
1460
  );
1340
1461
  }
@@ -1377,9 +1498,14 @@ function withScope(callback) {
1377
1498
  }
1378
1499
  }
1379
1500
  function startTransaction(name, op) {
1501
+ const active = currentRunTransaction();
1502
+ if (active) {
1503
+ client?.logger.debug(`startTransaction('${name}') dentro de uma execu\xE7\xE3o \u2014 reutilizando a transaction da run`);
1504
+ return active;
1505
+ }
1380
1506
  const rate = client?.options.tracesSampleRate ?? 1;
1381
1507
  const sampled = rate >= 1 || Math.random() < rate;
1382
- return new Transaction(client, name, op, sampled, currentRunId());
1508
+ return new Transaction(client, name, op, sampled);
1383
1509
  }
1384
1510
  function checkIn(options) {
1385
1511
  return client ? sendCheckIn(client, options) : void 0;
@@ -1401,17 +1527,17 @@ function register(task, handler) {
1401
1527
  if (registry.has(task)) {
1402
1528
  (agent?.logger ?? client?.logger)?.warn(`register('${task}') sobrescreveu um handler existente`);
1403
1529
  }
1404
- if (client && !agent && !warnedManagerDisabled) {
1405
- warnedManagerDisabled = true;
1530
+ if (client && !agent && !warnedTasksDisabled) {
1531
+ warnedTasksDisabled = true;
1406
1532
  client.logger.warn(
1407
- `register('${task}') com o Manager desabilitado \u2014 passe manager: true (ou op\xE7\xF5es) no init()`
1533
+ `register('${task}') com tasks desabilitadas \u2014 passe tasks: true (ou op\xE7\xF5es) no init()`
1408
1534
  );
1409
1535
  }
1410
1536
  registry.set(task, handler);
1411
1537
  }
1412
1538
  function start() {
1413
1539
  if (!agent) {
1414
- throw new Error("[hobots] Manager n\xE3o configurado \u2014 passe manager: true (ou op\xE7\xF5es) no init() antes de start()");
1540
+ throw new Error("[hobots] tasks n\xE3o configuradas \u2014 passe tasks: true (ou op\xE7\xF5es) no init() antes de start()");
1415
1541
  }
1416
1542
  agent.start();
1417
1543
  }
@@ -1428,9 +1554,6 @@ async function close(timeoutMs) {
1428
1554
  export {
1429
1555
  Client,
1430
1556
  DEFAULT_BASE_URL,
1431
- ManagerAgent,
1432
- ManagerApi,
1433
- ManagerApiError,
1434
1557
  Scope,
1435
1558
  Span,
1436
1559
  Transaction,
@@ -1443,7 +1566,7 @@ export {
1443
1566
  getClient,
1444
1567
  init,
1445
1568
  register,
1446
- resolveConfig,
1569
+ scrubSecrets,
1447
1570
  setContext,
1448
1571
  setExtra,
1449
1572
  setTag,