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.cjs CHANGED
@@ -32,9 +32,6 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  Client: () => Client,
34
34
  DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
35
- ManagerAgent: () => ManagerAgent,
36
- ManagerApi: () => ManagerApi,
37
- ManagerApiError: () => ManagerApiError,
38
35
  Scope: () => Scope,
39
36
  Span: () => Span,
40
37
  Transaction: () => Transaction,
@@ -47,7 +44,7 @@ __export(src_exports, {
47
44
  getClient: () => getClient,
48
45
  init: () => init,
49
46
  register: () => register,
50
- resolveConfig: () => resolveConfig,
47
+ scrubSecrets: () => scrubSecrets,
51
48
  setContext: () => setContext,
52
49
  setExtra: () => setExtra,
53
50
  setTag: () => setTag,
@@ -63,26 +60,52 @@ module.exports = __toCommonJS(src_exports);
63
60
 
64
61
  // src/config.ts
65
62
  var DEFAULT_BASE_URL = "https://api2.hobots.app";
63
+ var SECRET_PATTERN = /^hb_[a-f0-9]{64}$/i;
66
64
  function envBaseUrl() {
67
65
  if (typeof process === "undefined" || !process.env) return void 0;
68
66
  return process.env.HOBOTS_API_URL?.trim() || void 0;
69
67
  }
68
+ function isLoopbackHost(hostname2) {
69
+ return hostname2 === "localhost" || hostname2.endsWith(".localhost") || hostname2.startsWith("127.") || hostname2 === "[::1]" || hostname2 === "::1" || hostname2 === "0.0.0.0";
70
+ }
71
+ var warnedInsecureBaseUrl = false;
72
+ function validateBaseUrl(raw, prefix) {
73
+ let url;
74
+ try {
75
+ url = new URL(raw);
76
+ } catch {
77
+ throw new Error(`${prefix} HOBOTS_API_URL inv\xE1lida \u2014 esperado URL http(s), ex.: http://localhost:3001`);
78
+ }
79
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
80
+ throw new Error(`${prefix} HOBOTS_API_URL com esquema "${url.protocol}" n\xE3o suportado \u2014 use http(s)`);
81
+ }
82
+ if (url.username || url.password) {
83
+ 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`);
84
+ }
85
+ if (url.protocol === "http:" && !isLoopbackHost(url.hostname) && !warnedInsecureBaseUrl) {
86
+ warnedInsecureBaseUrl = true;
87
+ console.warn(
88
+ `${prefix} HOBOTS_API_URL usa http para o host remoto "${url.hostname}" \u2014 o Client Secret trafega sem TLS`
89
+ );
90
+ }
91
+ return raw.replace(/\/+$/, "");
92
+ }
70
93
  function resolveConfig(options, prefix = "[hobots]") {
71
94
  const key = options.clientSecret?.trim();
72
95
  if (!key) {
73
96
  throw new Error(`${prefix} clientSecret ausente \u2014 copie o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos`);
74
97
  }
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`);
98
+ if (!SECRET_PATTERN.test(key)) {
99
+ throw new Error(
100
+ `${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`
101
+ );
80
102
  }
103
+ const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
81
104
  return {
82
105
  key,
83
106
  baseUrl,
84
107
  ingestEndpoint: `${baseUrl}/v1/ingest`,
85
- managerBaseUrl: `${baseUrl}/v1/manager`
108
+ tasksBaseUrl: `${baseUrl}/v1/tasks`
86
109
  };
87
110
  }
88
111
 
@@ -119,9 +142,28 @@ function parseStacktrace(stack) {
119
142
  return frames;
120
143
  }
121
144
 
145
+ // src/scrub.ts
146
+ var SECRET_PATTERNS = [
147
+ // client secret da Hobots
148
+ [/hb_[a-f0-9]{16,}/gi, "[REDACTED]"],
149
+ // tokens bearer em headers/mensagens
150
+ [/\b(authorization:?\s*)?bearer\s+[a-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED]"],
151
+ // chaves no formato sk-… (OpenAI e afins)
152
+ [/\bsk-[a-z0-9_-]{16,}\b/gi, "[REDACTED]"],
153
+ // credenciais embutidas em URL (https://user:pass@host)
154
+ [/\/\/[^/\s:@]+:[^/\s@]+@/g, "//[REDACTED]@"]
155
+ ];
156
+ function scrubSecrets(text) {
157
+ let out = text;
158
+ for (const [pattern, replacement] of SECRET_PATTERNS) {
159
+ out = out.replace(pattern, replacement);
160
+ }
161
+ return out;
162
+ }
163
+
122
164
  // src/utils.ts
123
165
  var SDK_NAME = "hobots";
124
- var SDK_VERSION = true ? "1.5.0" : "0.0.0-dev";
166
+ var SDK_VERSION = true ? "2.0.0" : "0.0.0-dev";
125
167
  function isNode() {
126
168
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
127
169
  }
@@ -130,6 +172,12 @@ function uuid4() {
130
172
  if (cryptoRef && typeof cryptoRef.randomUUID === "function") {
131
173
  return cryptoRef.randomUUID().replace(/-/g, "");
132
174
  }
175
+ if (cryptoRef && typeof cryptoRef.getRandomValues === "function") {
176
+ const bytes = cryptoRef.getRandomValues(new Uint8Array(16));
177
+ bytes[6] = bytes[6] & 15 | 64;
178
+ bytes[8] = bytes[8] & 63 | 128;
179
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
180
+ }
133
181
  let out = "";
134
182
  for (let i = 0; i < 32; i++) {
135
183
  out += Math.floor(Math.random() * 16).toString(16);
@@ -148,7 +196,69 @@ function sleep(ms) {
148
196
  unrefTimer(timer);
149
197
  });
150
198
  }
151
- var PREFIX = "[hobots-monitor]";
199
+ function jitter(baseMs) {
200
+ return Math.floor(baseMs / 2 + Math.random() * (baseMs / 2));
201
+ }
202
+ function truncate(value, max) {
203
+ return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
204
+ }
205
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
206
+ async function fetchWithRetry(send, options) {
207
+ const delays = options.delays ?? DEFAULT_RETRY_DELAYS_MS;
208
+ for (let attempt = 0; ; attempt++) {
209
+ let retriable;
210
+ let serverDelay;
211
+ try {
212
+ const response = await send();
213
+ if (response.ok) return response;
214
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) return response;
215
+ if (response.status === 429) serverDelay = retryAfterMs(response);
216
+ retriable = new Error(`HTTP ${response.status}`);
217
+ } catch (error) {
218
+ retriable = error;
219
+ }
220
+ const base = delays[attempt];
221
+ if (base === void 0) {
222
+ throw retriable instanceof Error ? retriable : new Error(String(retriable));
223
+ }
224
+ const delay = serverDelay ?? jitter(base);
225
+ options.logger.warn(`${options.label}: falha (${String(retriable)}), retry em ${delay}ms`);
226
+ await sleep(delay);
227
+ }
228
+ }
229
+ var MAX_RETRY_AFTER_MS = 6e4;
230
+ function retryAfterMs(response) {
231
+ const header = response.headers.get("retry-after");
232
+ if (!header) return void 0;
233
+ const seconds = Number(header);
234
+ if (Number.isFinite(seconds)) {
235
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, seconds * 1e3));
236
+ }
237
+ const date = Date.parse(header);
238
+ if (Number.isNaN(date)) return void 0;
239
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, date - Date.now()));
240
+ }
241
+ function withTimeoutSignal(timeoutMs, external) {
242
+ const controller = new AbortController();
243
+ const onAbort = () => controller.abort(external?.reason);
244
+ if (external?.aborted) {
245
+ onAbort();
246
+ } else {
247
+ external?.addEventListener("abort", onAbort, { once: true });
248
+ }
249
+ const timer = setTimeout(() => {
250
+ controller.abort(Object.assign(new Error(`timeout ap\xF3s ${timeoutMs}ms`), { name: "TimeoutError" }));
251
+ }, timeoutMs);
252
+ unrefTimer(timer);
253
+ return {
254
+ signal: controller.signal,
255
+ cleanup: () => {
256
+ clearTimeout(timer);
257
+ external?.removeEventListener("abort", onAbort);
258
+ }
259
+ };
260
+ }
261
+ var PREFIX = "[hobots]";
152
262
  function makeLogger(enabled, prefix = PREFIX) {
153
263
  return {
154
264
  debug: (...args) => enabled && console.debug(colorize(prefix, "gray"), ...args),
@@ -189,11 +299,11 @@ function eventFromException(error, level = "error") {
189
299
  let frames = [];
190
300
  if (error instanceof Error) {
191
301
  type = error.name || "Error";
192
- value = error.message;
302
+ value = scrubSecrets(error.message);
193
303
  frames = parseStacktrace(error.stack);
194
304
  } else {
195
305
  type = "NonError";
196
- value = safeString(error);
306
+ value = scrubSecrets(safeString(error));
197
307
  }
198
308
  return {
199
309
  event_id: uuid4(),
@@ -211,12 +321,12 @@ function eventFromMessage(message, level = "info") {
211
321
  event_id: uuid4(),
212
322
  timestamp: nowISO(),
213
323
  level,
214
- message
324
+ message: scrubSecrets(message)
215
325
  };
216
326
  }
217
327
 
218
328
  // src/transport.ts
219
- var RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
329
+ var REQUEST_TIMEOUT_MS = 1e4;
220
330
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
221
331
  var DEFAULT_MAX_BATCH_SIZE = 10;
222
332
  var DEFAULT_MAX_QUEUE_SIZE = 200;
@@ -282,32 +392,26 @@ var Transport = class {
282
392
  return request;
283
393
  }
284
394
  async sendWithRetry(envelope) {
285
- for (let attempt = 0; ; attempt++) {
286
- try {
287
- const response = await fetch(this.endpoint, {
395
+ let response;
396
+ try {
397
+ response = await fetchWithRetry(
398
+ () => fetch(this.endpoint, {
288
399
  method: "POST",
289
400
  headers: { "content-type": "application/json", authorization: `Bearer ${this.key}` },
290
- body: JSON.stringify(envelope)
291
- });
292
- if (response.ok) {
293
- this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
294
- return;
295
- }
296
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
297
- this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
298
- return;
299
- }
300
- throw new Error(`HTTP ${response.status}`);
301
- } catch (error) {
302
- const delay = RETRY_DELAYS_MS[attempt];
303
- if (delay === void 0) {
304
- this.logger.error("envelope descartado ap\xF3s retries:", error);
305
- return;
306
- }
307
- this.logger.warn(`falha no envio (${String(error)}), retry em ${delay}ms`);
308
- await sleep(delay);
309
- }
401
+ body: JSON.stringify(envelope),
402
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
403
+ }),
404
+ { logger: this.logger, label: "envio de envelope" }
405
+ );
406
+ } catch (error) {
407
+ this.logger.error("envelope descartado ap\xF3s retries:", error);
408
+ return;
310
409
  }
410
+ if (response.ok) {
411
+ this.logger.debug(`envelope enviado (${envelope.items.length} itens)`);
412
+ return;
413
+ }
414
+ this.logger.error(`envelope rejeitado (HTTP ${response.status}), descartado`);
311
415
  }
312
416
  /** Envia o que estiver na fila e aguarda envios em andamento (até timeoutMs). */
313
417
  async flush(timeoutMs = 5e3) {
@@ -333,22 +437,38 @@ function runtimeContexts() {
333
437
  return { runtime: { name: "browser" } };
334
438
  }
335
439
  var Client = class {
336
- options;
337
- config;
338
440
  logger;
441
+ // campos privados + getters de protótipo: o clientSecret não aparece em
442
+ // JSON.stringify/serialização enumerável do Client (ex.: um objeto capturado
443
+ // que referencie o client), mas client.options/config seguem funcionando
444
+ #options;
445
+ #config;
339
446
  transport;
340
- constructor(options) {
341
- this.options = options;
447
+ constructor(options, config = resolveConfig(options)) {
448
+ this.#options = options;
342
449
  this.logger = makeLogger(!!options.debug);
343
- this.config = resolveConfig(options);
450
+ this.#config = config;
344
451
  this.transport = new Transport({
345
- endpoint: this.config.ingestEndpoint,
346
- key: this.config.key,
452
+ endpoint: this.#config.ingestEndpoint,
453
+ key: this.#config.key,
347
454
  logger: this.logger,
348
455
  flushIntervalMs: options.flushIntervalMs,
349
456
  maxBatchSize: options.maxBatchSize
350
457
  });
351
458
  }
459
+ get options() {
460
+ return this.#options;
461
+ }
462
+ get config() {
463
+ return this.#config;
464
+ }
465
+ /** Serialização explícita nunca inclui o secret. */
466
+ toJSON() {
467
+ return { sdk: SDK_NAME, version: SDK_VERSION, baseUrl: this.#config.baseUrl };
468
+ }
469
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
470
+ return `Client { baseUrl: '${this.#config.baseUrl}' }`;
471
+ }
352
472
  captureException(error, hint, scope2) {
353
473
  return this.captureEvent(eventFromException(error, hint?.level ?? "error"), hint, scope2);
354
474
  }
@@ -733,48 +853,31 @@ function sendCheckIn(client2, options) {
733
853
  return checkInId;
734
854
  }
735
855
  async function withMonitorImpl(client2, captureError, monitorSlug, fn, options) {
736
- const checkInId = sendCheckIn(client2, {
737
- monitorSlug,
738
- status: "in_progress",
739
- schedule: options?.schedule,
740
- timezone: options?.timezone
741
- });
856
+ const base = { monitorSlug, schedule: options?.schedule, timezone: options?.timezone };
857
+ const checkInId = sendCheckIn(client2, { ...base, status: "in_progress" });
742
858
  const startedAt = Date.now();
743
859
  try {
744
860
  const result = await fn();
745
- sendCheckIn(client2, {
746
- monitorSlug,
747
- status: "ok",
748
- checkInId,
749
- durationMs: Date.now() - startedAt,
750
- schedule: options?.schedule,
751
- timezone: options?.timezone
752
- });
861
+ sendCheckIn(client2, { ...base, status: "ok", checkInId, durationMs: Date.now() - startedAt });
753
862
  return result;
754
863
  } catch (error) {
755
- sendCheckIn(client2, {
756
- monitorSlug,
757
- status: "error",
758
- checkInId,
759
- durationMs: Date.now() - startedAt,
760
- schedule: options?.schedule,
761
- timezone: options?.timezone
762
- });
864
+ sendCheckIn(client2, { ...base, status: "error", checkInId, durationMs: Date.now() - startedAt });
763
865
  captureError(error);
764
866
  throw error;
765
867
  }
766
868
  }
767
869
 
768
- // src/manager/api.ts
769
- var RETRY_DELAYS_MS2 = [1e3, 4e3, 16e3];
770
- var ManagerApiError = class extends Error {
870
+ // src/tasks/api.ts
871
+ var POLL_TIMEOUT_MS = 3e4;
872
+ var UPDATE_TIMEOUT_MS = 1e4;
873
+ var TasksApiError = class extends Error {
771
874
  constructor(message, status) {
772
875
  super(message);
773
876
  this.status = status;
774
- this.name = "ManagerApiError";
877
+ this.name = "TasksApiError";
775
878
  }
776
879
  };
777
- var ManagerApi = class {
880
+ var TasksApi = class {
778
881
  baseUrl;
779
882
  key;
780
883
  logger;
@@ -791,45 +894,61 @@ var ManagerApi = class {
791
894
  }
792
895
  /** Um request só — o loop de polling é quem decide o backoff entre tentativas. */
793
896
  async poll(request, signal) {
794
- const response = await fetch(`${this.baseUrl}/poll`, {
795
- method: "POST",
796
- headers: this.headers(),
797
- body: JSON.stringify(request),
798
- signal
799
- });
897
+ const { signal: timeoutSignal, cleanup } = withTimeoutSignal(POLL_TIMEOUT_MS, signal);
898
+ let response;
899
+ try {
900
+ response = await fetch(`${this.baseUrl}/poll`, {
901
+ method: "POST",
902
+ headers: this.headers(),
903
+ body: JSON.stringify(request),
904
+ signal: timeoutSignal
905
+ });
906
+ } finally {
907
+ cleanup();
908
+ }
800
909
  if (!response.ok) {
801
- throw new ManagerApiError(`poll falhou (HTTP ${response.status})`, response.status);
910
+ throw new TasksApiError(`poll falhou (HTTP ${response.status})`, response.status);
911
+ }
912
+ return this.parsePollResponse(await response.json(), request.max ?? 1);
913
+ }
914
+ /** Nunca confia no shape da resposta: runs malformadas são descartadas e o `max` é respeitado. */
915
+ parsePollResponse(body, max) {
916
+ const runs = body?.runs;
917
+ if (!Array.isArray(runs)) {
918
+ throw new TasksApiError("resposta inv\xE1lida do poll (campo runs ausente)", 0);
919
+ }
920
+ const valid = [];
921
+ for (const run of runs.slice(0, Math.max(0, max))) {
922
+ if (typeof run?.id !== "string" || typeof run.task !== "string") continue;
923
+ const params = run.params && typeof run.params === "object" && !Array.isArray(run.params) ? run.params : {};
924
+ valid.push({ ...run, id: run.id, task: run.task, params });
925
+ }
926
+ if (valid.length < runs.length) {
927
+ this.logger.warn(`poll retornou ${runs.length - valid.length} run(s) inv\xE1lida(s)/excedente(s) \u2014 descartadas`);
802
928
  }
803
- return await response.json();
929
+ return { runs: valid };
804
930
  }
805
931
  /**
806
932
  * PATCH de status/logs com retry em falha de rede/5xx/429.
807
- * 4xx é definitivo e vira ManagerApiError imediato (409 = run cancelada/transição inválida).
933
+ * 4xx é definitivo e vira TasksApiError imediato (409 = run cancelada/transição inválida).
808
934
  */
809
935
  async updateRun(runId, update) {
810
- for (let attempt = 0; ; attempt++) {
811
- let retriable;
812
- try {
813
- const response = await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
936
+ let response;
937
+ try {
938
+ response = await fetchWithRetry(
939
+ () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
814
940
  method: "PATCH",
815
941
  headers: this.headers(),
816
- body: JSON.stringify(update)
817
- });
818
- if (response.ok) return;
819
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
820
- throw new ManagerApiError(`update rejeitado (HTTP ${response.status})`, response.status);
821
- }
822
- retriable = new Error(`HTTP ${response.status}`);
823
- } catch (error) {
824
- if (error instanceof ManagerApiError) throw error;
825
- retriable = error;
826
- }
827
- const delay = RETRY_DELAYS_MS2[attempt];
828
- if (delay === void 0) {
829
- throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(retriable)}`);
830
- }
831
- this.logger.warn(`falha no update da run ${runId} (${String(retriable)}), retry em ${delay}ms`);
832
- await sleep(delay);
942
+ body: JSON.stringify(update),
943
+ signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
944
+ }),
945
+ { logger: this.logger, label: `update da run ${runId}` }
946
+ );
947
+ } catch (error) {
948
+ throw new Error(`update da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
949
+ }
950
+ if (!response.ok) {
951
+ throw new TasksApiError(`update rejeitado (HTTP ${response.status})`, response.status);
833
952
  }
834
953
  }
835
954
  /**
@@ -847,7 +966,7 @@ var ManagerApi = class {
847
966
  }
848
967
  };
849
968
 
850
- // src/manager/context.ts
969
+ // src/tasks/context.ts
851
970
  var LOG_LEVEL_COLOR = {
852
971
  debug: "gray",
853
972
  info: "cyan",
@@ -951,14 +1070,14 @@ var RunReporter = class {
951
1070
  }
952
1071
  log(level, message) {
953
1072
  if (this.dead) return;
954
- printRunLog(this.context.task, level, message);
1073
+ const scrubbed = scrubSecrets(message);
1074
+ printRunLog(this.context.task, level, scrubbed);
955
1075
  if (this.loggedCount >= MAX_LOGS_PER_RUN) {
956
1076
  this.droppedCount++;
957
1077
  return;
958
1078
  }
959
1079
  this.loggedCount++;
960
- const truncated = message.length > MAX_LOG_MESSAGE_CHARS ? `${message.slice(0, MAX_LOG_MESSAGE_CHARS)}\u2026 [truncado]` : message;
961
- this.buffer.push({ ts: nowISO(), level, message: truncated });
1080
+ this.buffer.push({ ts: nowISO(), level, message: truncate(scrubbed, MAX_LOG_MESSAGE_CHARS) });
962
1081
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
963
1082
  this.flushLogs();
964
1083
  } else {
@@ -1049,7 +1168,7 @@ var RunReporter = class {
1049
1168
  try {
1050
1169
  await this.api.updateRun(this.runId, payload);
1051
1170
  } catch (error) {
1052
- if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
1171
+ if (error instanceof TasksApiError && (error.status === 409 || error.status === 404)) {
1053
1172
  this.dead = true;
1054
1173
  this.stopCancelWatch();
1055
1174
  this.abort();
@@ -1065,7 +1184,7 @@ var RunReporter = class {
1065
1184
  }
1066
1185
  };
1067
1186
 
1068
- // src/manager/run-context-store.ts
1187
+ // src/tasks/run-context-store.ts
1069
1188
  var storage;
1070
1189
  var initPromise;
1071
1190
  async function ensureStorage() {
@@ -1075,15 +1194,15 @@ async function ensureStorage() {
1075
1194
  });
1076
1195
  await initPromise;
1077
1196
  }
1078
- async function runWithRunId(runId, fn) {
1197
+ async function runWithRunContext(store, fn) {
1079
1198
  await ensureStorage();
1080
- return storage ? storage.run({ runId }, fn) : fn();
1199
+ return storage ? storage.run(store, fn) : fn();
1081
1200
  }
1082
- function currentRunId() {
1083
- return storage?.getStore()?.runId;
1201
+ function currentRunTransaction() {
1202
+ return storage?.getStore()?.tx;
1084
1203
  }
1085
1204
 
1086
- // src/manager/shutdown.ts
1205
+ // src/tasks/shutdown.ts
1087
1206
  var WATCHED_SIGNALS = ["SIGINT", "SIGTERM"];
1088
1207
  function installShutdownHandlers(agent2) {
1089
1208
  if (!isNode()) return () => {
@@ -1127,7 +1246,7 @@ function installShutdownHandlers(agent2) {
1127
1246
  };
1128
1247
  }
1129
1248
 
1130
- // src/manager/agent.ts
1249
+ // src/tasks/agent.ts
1131
1250
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
1132
1251
  var MIN_POLL_INTERVAL_MS = 1e3;
1133
1252
  var MAX_CONCURRENCY = 5;
@@ -1137,13 +1256,13 @@ function hostname() {
1137
1256
  if (!isNode()) return void 0;
1138
1257
  return process.env.HOSTNAME || process.env.COMPUTERNAME || void 0;
1139
1258
  }
1140
- var ManagerAgent = class {
1259
+ var TaskAgent = class {
1141
1260
  constructor(options, registry2) {
1142
1261
  this.options = options;
1143
1262
  this.registry = registry2;
1144
- this.logger = makeLogger(options.debug ?? false, "[hobots-manager]");
1145
- const config = resolveConfig(options, "[hobots-manager]");
1146
- this.api = new ManagerApi({ baseUrl: config.managerBaseUrl, key: config.key, logger: this.logger });
1263
+ this.logger = makeLogger(options.debug ?? false);
1264
+ const config = options.config;
1265
+ this.api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger: this.logger });
1147
1266
  this.client = options.client;
1148
1267
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
1149
1268
  this.concurrency = Math.min(MAX_CONCURRENCY, Math.max(1, options.concurrency ?? 1));
@@ -1151,7 +1270,7 @@ var ManagerAgent = class {
1151
1270
  this.taskTimeoutMs = options.taskTimeoutMs;
1152
1271
  this.logFlushIntervalMs = options.logFlushIntervalMs;
1153
1272
  this.cancelCheckIntervalMs = options.cancelCheckIntervalMs;
1154
- this.logger.debug("agente configurado", { baseUrl: config.managerBaseUrl });
1273
+ this.logger.debug("agente configurado", { baseUrl: config.tasksBaseUrl });
1155
1274
  }
1156
1275
  logger;
1157
1276
  api;
@@ -1176,10 +1295,10 @@ var ManagerAgent = class {
1176
1295
  start() {
1177
1296
  if (this.state === "started") return;
1178
1297
  if (this.state === "stopping") {
1179
- throw new Error("[hobots-manager] start() chamado durante stop() \u2014 aguarde o stop terminar");
1298
+ throw new Error("[hobots] start() chamado durante stop() \u2014 aguarde o stop terminar");
1180
1299
  }
1181
1300
  if (this.registry.size === 0) {
1182
- throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
1301
+ throw new Error("[hobots] nenhuma task registrada \u2014 chame register() antes de start()");
1183
1302
  }
1184
1303
  this.state = "started";
1185
1304
  if (this.options.installExitHandlers !== false && !this.uninstallShutdown) {
@@ -1261,6 +1380,7 @@ var ManagerAgent = class {
1261
1380
  this.consecutiveFailures = 0;
1262
1381
  this.consecutiveAuthFailures = 0;
1263
1382
  for (const run of response.runs) {
1383
+ if (this.activeRuns.size >= this.concurrency) break;
1264
1384
  this.dispatch(run);
1265
1385
  }
1266
1386
  this.schedule(response.runs.length > 0 ? 0 : this.pollIntervalMs);
@@ -1272,7 +1392,7 @@ var ManagerAgent = class {
1272
1392
  }
1273
1393
  }
1274
1394
  delayAfterPollError(error) {
1275
- if (error instanceof ManagerApiError && error.status === 401) {
1395
+ if (error instanceof TasksApiError && error.status === 401) {
1276
1396
  this.consecutiveAuthFailures++;
1277
1397
  if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
1278
1398
  this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos");
@@ -1281,7 +1401,7 @@ var ManagerAgent = class {
1281
1401
  }
1282
1402
  this.consecutiveFailures++;
1283
1403
  const ladder = POLL_RETRY_DELAYS_MS[Math.min(this.consecutiveFailures - 1, POLL_RETRY_DELAYS_MS.length - 1)];
1284
- const delay = Math.min(Math.max(ladder, 0), Math.max(this.pollIntervalMs, 16e3));
1404
+ const delay = Math.min(Math.max(jitter(ladder), 0), Math.max(this.pollIntervalMs, 16e3));
1285
1405
  this.logger.warn(`poll falhou (${String(error)}), pr\xF3xima tentativa em ${delay}ms`);
1286
1406
  return delay;
1287
1407
  }
@@ -1293,7 +1413,7 @@ var ManagerAgent = class {
1293
1413
  this.activeRuns.add(promise);
1294
1414
  }
1295
1415
  async executeRun(run) {
1296
- const tx = new Transaction(this.client, `run:${run.task}`, "manager.run", true, run.id);
1416
+ const tx = new Transaction(this.client, `run:${run.task}`, "hobots.task", true, run.id);
1297
1417
  const reporter = new RunReporter(this.api, run.id, run.task, this.logger, tx, this.logFlushIntervalMs, this.cancelCheckIntervalMs);
1298
1418
  this.reporters.add(reporter);
1299
1419
  try {
@@ -1317,7 +1437,7 @@ var ManagerAgent = class {
1317
1437
  }
1318
1438
  this.logger.debug(`executando run ${run.id} (${run.task})`);
1319
1439
  try {
1320
- await runWithRunId(run.id, () => this.invokeWithTimeout(handler, run, reporter));
1440
+ await runWithRunContext({ runId: run.id, tx }, () => this.invokeWithTimeout(handler, run, reporter));
1321
1441
  reporter.reportSuccess();
1322
1442
  tx.setStatus("ok");
1323
1443
  this.logger.debug(`run ${run.id} conclu\xEDda`);
@@ -1352,18 +1472,15 @@ var ManagerAgent = class {
1352
1472
  var TIMEOUT_SENTINEL = /* @__PURE__ */ Symbol("task-timeout");
1353
1473
  var MAX_ERROR_MESSAGE_CHARS = 1e4;
1354
1474
  var MAX_ERROR_STACK_CHARS = 2e4;
1355
- function truncate(value, max) {
1356
- return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
1357
- }
1358
1475
  function toRunError(error) {
1359
1476
  if (error instanceof Error) {
1360
1477
  return {
1361
1478
  type: error.name,
1362
- message: truncate(error.message, MAX_ERROR_MESSAGE_CHARS),
1363
- stack: error.stack ? truncate(error.stack, MAX_ERROR_STACK_CHARS) : void 0
1479
+ message: scrubSecrets(truncate(error.message, MAX_ERROR_MESSAGE_CHARS)),
1480
+ stack: error.stack ? scrubSecrets(truncate(error.stack, MAX_ERROR_STACK_CHARS)) : void 0
1364
1481
  };
1365
1482
  }
1366
- return { message: truncate(String(error), MAX_ERROR_MESSAGE_CHARS) };
1483
+ return { message: scrubSecrets(truncate(String(error), MAX_ERROR_MESSAGE_CHARS)) };
1367
1484
  }
1368
1485
 
1369
1486
  // src/core.ts
@@ -1373,7 +1490,7 @@ var scope = new Scope();
1373
1490
  var heartbeat;
1374
1491
  var agent;
1375
1492
  var registry = /* @__PURE__ */ new Map();
1376
- var warnedManagerDisabled = false;
1493
+ var warnedTasksDisabled = false;
1377
1494
  function init(options) {
1378
1495
  if (client) {
1379
1496
  client.logger.warn("init() chamado mais de uma vez \u2014 substituindo o client anterior");
@@ -1384,8 +1501,9 @@ function init(options) {
1384
1501
  void agent.stop(0);
1385
1502
  agent = void 0;
1386
1503
  }
1387
- warnedManagerDisabled = false;
1388
- client = new Client(options);
1504
+ warnedTasksDisabled = false;
1505
+ const config = resolveConfig(options);
1506
+ client = new Client(options, config);
1389
1507
  if (options.captureUnhandled !== false) {
1390
1508
  installGlobalHandlers(client, () => scope);
1391
1509
  }
@@ -1394,10 +1512,10 @@ function init(options) {
1394
1512
  heartbeat = new Heartbeat(client, heartbeatOptions, options.instanceId);
1395
1513
  heartbeat.start();
1396
1514
  }
1397
- if (options.manager) {
1398
- const managerOptions = options.manager === true ? {} : options.manager;
1399
- agent = new ManagerAgent(
1400
- { ...managerOptions, clientSecret: options.clientSecret, instanceId: options.instanceId, debug: options.debug, client },
1515
+ if (options.tasks) {
1516
+ const tasksOptions = options.tasks === true ? {} : options.tasks;
1517
+ agent = new TaskAgent(
1518
+ { ...tasksOptions, config, instanceId: options.instanceId, debug: options.debug, client },
1401
1519
  registry
1402
1520
  );
1403
1521
  }
@@ -1440,9 +1558,14 @@ function withScope(callback) {
1440
1558
  }
1441
1559
  }
1442
1560
  function startTransaction(name, op) {
1561
+ const active = currentRunTransaction();
1562
+ if (active) {
1563
+ client?.logger.debug(`startTransaction('${name}') dentro de uma execu\xE7\xE3o \u2014 reutilizando a transaction da run`);
1564
+ return active;
1565
+ }
1443
1566
  const rate = client?.options.tracesSampleRate ?? 1;
1444
1567
  const sampled = rate >= 1 || Math.random() < rate;
1445
- return new Transaction(client, name, op, sampled, currentRunId());
1568
+ return new Transaction(client, name, op, sampled);
1446
1569
  }
1447
1570
  function checkIn(options) {
1448
1571
  return client ? sendCheckIn(client, options) : void 0;
@@ -1464,17 +1587,17 @@ function register(task, handler) {
1464
1587
  if (registry.has(task)) {
1465
1588
  (agent?.logger ?? client?.logger)?.warn(`register('${task}') sobrescreveu um handler existente`);
1466
1589
  }
1467
- if (client && !agent && !warnedManagerDisabled) {
1468
- warnedManagerDisabled = true;
1590
+ if (client && !agent && !warnedTasksDisabled) {
1591
+ warnedTasksDisabled = true;
1469
1592
  client.logger.warn(
1470
- `register('${task}') com o Manager desabilitado \u2014 passe manager: true (ou op\xE7\xF5es) no init()`
1593
+ `register('${task}') com tasks desabilitadas \u2014 passe tasks: true (ou op\xE7\xF5es) no init()`
1471
1594
  );
1472
1595
  }
1473
1596
  registry.set(task, handler);
1474
1597
  }
1475
1598
  function start() {
1476
1599
  if (!agent) {
1477
- throw new Error("[hobots] Manager n\xE3o configurado \u2014 passe manager: true (ou op\xE7\xF5es) no init() antes de start()");
1600
+ throw new Error("[hobots] tasks n\xE3o configuradas \u2014 passe tasks: true (ou op\xE7\xF5es) no init() antes de start()");
1478
1601
  }
1479
1602
  agent.start();
1480
1603
  }