hobots 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/stacktrace.ts
2
2
  var MAX_FRAMES = 50;
3
+ var MAX_FILENAME_CHARS = 1024;
3
4
  var FRAME_RE = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?(.+?)\)?$/;
4
5
  var LOCATION_RE = /^(.*?):(\d+):(\d+)$/;
5
6
  function isInApp(filename) {
@@ -22,7 +23,7 @@ function parseStacktrace(stack) {
22
23
  const filename = loc ? loc[1] : location;
23
24
  frames.push({
24
25
  function: fn || "<anonymous>",
25
- filename: filename || void 0,
26
+ filename: filename ? filename.slice(0, MAX_FILENAME_CHARS) : void 0,
26
27
  lineno: loc?.[2] ? Number(loc[2]) : void 0,
27
28
  colno: loc?.[3] ? Number(loc[3]) : void 0,
28
29
  in_app: isInApp(filename)
@@ -52,7 +53,7 @@ function scrubSecrets(text) {
52
53
 
53
54
  // src/utils.ts
54
55
  var SDK_NAME = "hobots";
55
- var SDK_VERSION = true ? "3.0.0" : "0.0.0-dev";
56
+ var SDK_VERSION = true ? "3.2.0" : "0.0.0-dev";
56
57
  function isNode() {
57
58
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
58
59
  }
@@ -108,9 +109,19 @@ function jitter(baseMs) {
108
109
  function truncate(value, max) {
109
110
  return value.length > max ? `${value.slice(0, max)}\u2026 [truncado]` : value;
110
111
  }
112
+ function safeString(value) {
113
+ if (typeof value === "string") return value;
114
+ try {
115
+ const json = JSON.stringify(value);
116
+ if (json && json !== "{}") return json;
117
+ } catch {
118
+ }
119
+ return String(value);
120
+ }
111
121
  var DEFAULT_RETRY_DELAYS_MS = [1e3, 4e3, 16e3];
112
122
  async function fetchWithRetry(send, options) {
113
123
  const delays = options.delays ?? DEFAULT_RETRY_DELAYS_MS;
124
+ const noRetry = options.noRetryStatuses ?? [];
114
125
  for (let attempt = 0; ; attempt++) {
115
126
  let retriable;
116
127
  let serverDelay;
@@ -118,6 +129,7 @@ async function fetchWithRetry(send, options) {
118
129
  const response = await send();
119
130
  if (response.ok) return response;
120
131
  if (response.status >= 400 && response.status < 500 && response.status !== 429) return response;
132
+ if (noRetry.includes(response.status)) return response;
121
133
  if (response.status === 429) serverDelay = retryAfterMs(response);
122
134
  retriable = new Error(`HTTP ${response.status}`);
123
135
  } catch (error) {
@@ -190,26 +202,18 @@ function colorize(text, color) {
190
202
  }
191
203
 
192
204
  // src/eventbuilder.ts
193
- function safeString(value) {
194
- if (typeof value === "string") return value;
195
- try {
196
- const json = JSON.stringify(value);
197
- if (json && json !== "{}") return json;
198
- } catch {
199
- }
200
- return String(value);
201
- }
205
+ var MAX_EXCEPTION_VALUE_CHARS = 8192;
202
206
  function eventFromException(error, type = "error") {
203
207
  let exceptionType;
204
208
  let value;
205
209
  let frames = [];
206
210
  if (error instanceof Error) {
207
211
  exceptionType = error.name || "Error";
208
- value = scrubSecrets(error.message);
212
+ value = truncate(scrubSecrets(error.message), MAX_EXCEPTION_VALUE_CHARS);
209
213
  frames = parseStacktrace(error.stack);
210
214
  } else {
211
215
  exceptionType = "NonError";
212
- value = scrubSecrets(safeString(error));
216
+ value = truncate(scrubSecrets(safeString(error)), MAX_EXCEPTION_VALUE_CHARS);
213
217
  }
214
218
  return {
215
219
  event_id: uuid4(),
@@ -335,6 +339,18 @@ var Transport = class {
335
339
  };
336
340
 
337
341
  // src/client.ts
342
+ var MAX_TAG_VALUE_CHARS = 512;
343
+ var MAX_FINGERPRINT_ITEMS = 10;
344
+ var MAX_FINGERPRINT_ITEM_CHARS = 255;
345
+ var MAX_TRANSACTION_NAME_CHARS = 500;
346
+ var MAX_STEPS = 200;
347
+ function clipTags(tags) {
348
+ return Object.fromEntries(Object.entries(tags).map(([key, value]) => [key, truncate(String(value), MAX_TAG_VALUE_CHARS)]));
349
+ }
350
+ function clipFingerprint(fingerprint) {
351
+ if (fingerprint === void 0) return void 0;
352
+ return fingerprint.slice(0, MAX_FINGERPRINT_ITEMS).map((item) => truncate(String(item), MAX_FINGERPRINT_ITEM_CHARS));
353
+ }
338
354
  function runtimeContexts() {
339
355
  if (isNode()) {
340
356
  return {
@@ -403,20 +419,27 @@ var Client = class {
403
419
  type: hint?.type ?? event.type ?? "error",
404
420
  release: event.release ?? this.options.release,
405
421
  environment: this.resolveEnvironment(event.environment),
406
- tags: Object.keys(tags).length > 0 ? tags : void 0,
422
+ tags: Object.keys(tags).length > 0 ? clipTags(tags) : void 0,
407
423
  extra: Object.keys(extra).length > 0 ? extra : void 0,
408
424
  contexts: { ...runtimeContexts(), ...machine, ...event.contexts },
409
- fingerprint: hint?.fingerprint ?? event.fingerprint
425
+ fingerprint: clipFingerprint(hint?.fingerprint ?? event.fingerprint)
410
426
  };
411
427
  }
412
428
  sendTransaction(payload) {
429
+ let steps = payload.steps;
430
+ if (steps.length > MAX_STEPS) {
431
+ this.logger.warn(`transaction com ${steps.length} steps \u2014 enviando as primeiras ${MAX_STEPS}`);
432
+ steps = steps.slice(0, MAX_STEPS);
433
+ }
413
434
  this.transport.enqueue({
414
435
  type: "transaction",
415
436
  payload: {
416
437
  ...payload,
438
+ name: truncate(payload.name || "(sem nome)", MAX_TRANSACTION_NAME_CHARS),
439
+ steps,
417
440
  release: payload.release ?? this.options.release,
418
441
  environment: this.resolveEnvironment(payload.environment),
419
- tags: { ...this.options.tags, ...payload.tags }
442
+ tags: clipTags({ ...this.options.tags, ...payload.tags })
420
443
  }
421
444
  });
422
445
  }
@@ -509,12 +532,19 @@ function resolveConfig(options = {}, prefix = "[hobots]") {
509
532
 
510
533
  // src/env.ts
511
534
  var INSTANCE_ID_ENV = "HOBOTS_INSTANCE_ID";
535
+ var ENVIRONMENT_ENV = "HOBOTS_ENVIRONMENT";
512
536
  function resolveInstanceId(option) {
513
537
  const fromOption = option?.trim();
514
538
  if (fromOption) return fromOption;
515
539
  if (typeof process === "undefined" || !process.env) return void 0;
516
540
  return process.env[INSTANCE_ID_ENV]?.trim() || void 0;
517
541
  }
542
+ function resolveEnvironmentOption(option) {
543
+ const fromOption = option?.trim();
544
+ if (fromOption) return fromOption;
545
+ if (typeof process === "undefined" || !process.env) return void 0;
546
+ return process.env[ENVIRONMENT_ENV]?.trim() || void 0;
547
+ }
518
548
  async function fallbackInstanceId() {
519
549
  if (!isNode()) return "browser";
520
550
  try {
@@ -593,6 +623,11 @@ function externalListenerCount(event) {
593
623
  var emergencyTasks = [];
594
624
  function addEmergencyTask(task) {
595
625
  emergencyTasks.push(task);
626
+ const forget = () => {
627
+ const index = emergencyTasks.indexOf(task);
628
+ if (index !== -1) emergencyTasks.splice(index, 1);
629
+ };
630
+ task.then(forget, forget);
596
631
  }
597
632
  async function awaitEmergencyTasks(capMs) {
598
633
  if (emergencyTasks.length === 0) return;
@@ -787,6 +822,8 @@ var MachineSampler = class {
787
822
  // src/tasks/api.ts
788
823
  var POLL_TIMEOUT_MS = 3e4;
789
824
  var UPDATE_TIMEOUT_MS = 1e4;
825
+ var ATTACHMENT_TERMINAL_STATUSES = [429, 501, 503];
826
+ var ITEMS_TERMINAL_STATUSES = [429];
790
827
  var UPLOAD_TIMEOUT_MS = 3e4;
791
828
  var UPLOAD_RETRY_DELAYS_MS = [1e3, 4e3];
792
829
  var TasksApiError = class extends Error {
@@ -796,6 +833,14 @@ var TasksApiError = class extends Error {
796
833
  this.name = "TasksApiError";
797
834
  }
798
835
  };
836
+ async function withRequestTimeout(timeoutMs, external, send) {
837
+ const { signal, cleanup } = withTimeoutSignal(timeoutMs, external);
838
+ try {
839
+ return await send(signal);
840
+ } finally {
841
+ cleanup();
842
+ }
843
+ }
799
844
  var TasksApi = class {
800
845
  baseUrl;
801
846
  key;
@@ -932,12 +977,9 @@ var TasksApi = class {
932
977
  body: JSON.stringify(request),
933
978
  signal: AbortSignal.timeout(UPDATE_TIMEOUT_MS)
934
979
  }),
935
- { logger: this.logger, label: `itens da run ${runId}` }
980
+ { logger: this.logger, label: `itens da run ${runId}`, noRetryStatuses: ITEMS_TERMINAL_STATUSES }
936
981
  );
937
982
  } catch (error) {
938
- if (error instanceof Error && error.message === "HTTP 429") {
939
- throw new TasksApiError("envio de itens rejeitado (HTTP 429)", 429);
940
- }
941
983
  throw new Error(`envio de itens da run ${runId} descartado ap\xF3s retries: ${String(error)}`);
942
984
  }
943
985
  if (!response.ok) {
@@ -954,21 +996,19 @@ var TasksApi = class {
954
996
  * "esta instalação não tem anexos" — o RunReporter degrada em silêncio a partir daí.
955
997
  */
956
998
  async createAttachment(runId, request, signal) {
957
- const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPDATE_TIMEOUT_MS, signal);
958
- let response;
959
- try {
960
- response = await fetchWithRetry(
961
- () => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
999
+ const response = await fetchWithRetry(
1000
+ () => withRequestTimeout(
1001
+ UPDATE_TIMEOUT_MS,
1002
+ signal,
1003
+ (timeoutSignal) => fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}/attachments`, {
962
1004
  method: "POST",
963
1005
  headers: this.headers(),
964
1006
  body: JSON.stringify(request),
965
1007
  signal: timeoutSignal
966
- }),
967
- { logger: this.logger, label: `anexo da run ${runId}` }
968
- );
969
- } finally {
970
- cleanup();
971
- }
1008
+ })
1009
+ ),
1010
+ { logger: this.logger, label: `anexo da run ${runId}`, noRetryStatuses: ATTACHMENT_TERMINAL_STATUSES }
1011
+ );
972
1012
  if (!response.ok) {
973
1013
  throw new TasksApiError(`cria\xE7\xE3o de anexo rejeitada (HTTP ${response.status})`, response.status);
974
1014
  }
@@ -988,22 +1028,20 @@ var TasksApi = class {
988
1028
  * está na assinatura da URL, e mandar o client secret para fora da API seria vazamento.
989
1029
  */
990
1030
  async uploadAttachment(url, headers, body, signal) {
991
- const { signal: timeoutSignal, cleanup } = withTimeoutSignal(UPLOAD_TIMEOUT_MS, signal);
992
- let response;
993
- try {
994
- response = await fetchWithRetry(
995
- () => fetch(url, {
1031
+ const response = await fetchWithRetry(
1032
+ () => withRequestTimeout(
1033
+ UPLOAD_TIMEOUT_MS,
1034
+ signal,
1035
+ (timeoutSignal) => fetch(url, {
996
1036
  method: "PUT",
997
1037
  headers,
998
1038
  // BodyInit não aceita Uint8Array<ArrayBufferLike> em todos os lib.dom; o buffer é o mesmo
999
1039
  body,
1000
1040
  signal: timeoutSignal
1001
- }),
1002
- { delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo" }
1003
- );
1004
- } finally {
1005
- cleanup();
1006
- }
1041
+ })
1042
+ ),
1043
+ { delays: UPLOAD_RETRY_DELAYS_MS, logger: this.logger, label: "upload de anexo" }
1044
+ );
1007
1045
  if (!response.ok) {
1008
1046
  throw new TasksApiError(`upload de anexo rejeitado (HTTP ${response.status})`, response.status);
1009
1047
  }
@@ -1041,10 +1079,12 @@ var Backoff = class {
1041
1079
  this.authFailures = 0;
1042
1080
  }
1043
1081
  next(error) {
1044
- if (error instanceof TasksApiError && error.status === 401) {
1082
+ if (error instanceof TasksApiError && (error.status === 401 || error.status === 402)) {
1045
1083
  this.authFailures++;
1046
1084
  if (this.authFailures === 1 || this.authFailures % 10 === 0) {
1047
- this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes");
1085
+ this.logger.error(
1086
+ error.status === 402 ? "agente fora do limite do plano (402) \u2014 fa\xE7a upgrade em Plano e cobran\xE7a para reativ\xE1-lo" : "chave rejeitada (401) \u2014 verifique o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes"
1087
+ );
1048
1088
  }
1049
1089
  return Math.max(this.floorMs, AUTH_FAILURE_INTERVAL_MS);
1050
1090
  }
@@ -1078,7 +1118,8 @@ function resolveDelay(delay, attempt, fallbackMs) {
1078
1118
  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
1079
1119
  }
1080
1120
  async function retryWithBackoff(fn, options = {}) {
1081
- const retries = Math.max(0, Math.floor(options.retries ?? DEFAULT_RETRIES));
1121
+ const requested = options.retries ?? DEFAULT_RETRIES;
1122
+ const retries = Number.isFinite(requested) ? Math.max(0, Math.floor(requested)) : DEFAULT_RETRIES;
1082
1123
  const wait = options.wait ?? sleep;
1083
1124
  for (let attempt = 1; ; attempt++) {
1084
1125
  if (options.signal?.aborted) throw runCanceledError();
@@ -1113,6 +1154,7 @@ function startsWith(bytes, signature, offset = 0) {
1113
1154
  return signature.every((byte, index) => bytes[offset + index] === byte);
1114
1155
  }
1115
1156
  var PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10];
1157
+ var MAX_PNG_DIMENSION = 2e4;
1116
1158
  var JPEG_MAGIC = [255, 216, 255];
1117
1159
  var RIFF_MAGIC = [82, 73, 70, 70];
1118
1160
  var WEBP_MAGIC = [87, 69, 66, 80];
@@ -1128,6 +1170,7 @@ function readPngDimensions(bytes) {
1128
1170
  const width = view.getUint32(16);
1129
1171
  const height = view.getUint32(20);
1130
1172
  if (width === 0 || height === 0) return void 0;
1173
+ if (width > MAX_PNG_DIMENSION || height > MAX_PNG_DIMENSION) return void 0;
1131
1174
  return { width, height };
1132
1175
  }
1133
1176
  async function toBytes(input) {
@@ -1144,6 +1187,10 @@ var LOG_LEVEL_COLOR = {
1144
1187
  error: "red",
1145
1188
  success: "green"
1146
1189
  };
1190
+ function joinLogArgs(message, rest) {
1191
+ if (rest.length === 0) return safeString(message);
1192
+ return [message, ...rest].map(safeString).join(" ");
1193
+ }
1147
1194
  function printRunLog(task, level, message) {
1148
1195
  const line = colorize(`[hobots:${task}] ${level.toUpperCase()}`, LOG_LEVEL_COLOR[level]);
1149
1196
  const method2 = level === "warning" ? "warn" : level === "success" ? "info" : level;
@@ -1201,11 +1248,11 @@ var RunReporter = class {
1201
1248
  Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
1202
1249
  );
1203
1250
  const log = {
1204
- debug: (message) => this.log("debug", message),
1205
- info: (message) => this.log("info", message),
1206
- warn: (message) => this.log("warning", message),
1207
- error: (message) => this.log("error", message),
1208
- success: (message) => this.log("success", message)
1251
+ debug: (message, ...rest) => this.log("debug", joinLogArgs(message, rest)),
1252
+ info: (message, ...rest) => this.log("info", joinLogArgs(message, rest)),
1253
+ warn: (message, ...rest) => this.log("warning", joinLogArgs(message, rest)),
1254
+ error: (message, ...rest) => this.log("error", joinLogArgs(message, rest)),
1255
+ success: (message, ...rest) => this.log("success", joinLogArgs(message, rest))
1209
1256
  };
1210
1257
  tx.setLifecycleSink((level, message) => this.push(level, message));
1211
1258
  const checkCanceled = () => this.checkCanceled();
@@ -1355,7 +1402,7 @@ var RunReporter = class {
1355
1402
  }
1356
1403
  log(level, message) {
1357
1404
  if (this.dead) return;
1358
- const scrubbed = scrubSecrets(message);
1405
+ const scrubbed = scrubSecrets(safeString(message));
1359
1406
  printRunLog(this.context.task, level, scrubbed);
1360
1407
  this.push(level, scrubbed, true);
1361
1408
  }
@@ -1414,13 +1461,25 @@ var RunReporter = class {
1414
1461
  this.stopCancelWatch();
1415
1462
  this.clearFlushTimer();
1416
1463
  this.forceFinalProgress();
1417
- this.enqueue({ status: "succeeded", logs: this.takeLogs() });
1464
+ this.enqueue({ status: "succeeded", logs: this.takeLogsForTerminal() });
1418
1465
  }
1419
1466
  reportFailure(error) {
1420
1467
  this.stopCancelWatch();
1421
1468
  this.clearFlushTimer();
1422
1469
  this.forceFinalProgress();
1423
- this.enqueue({ status: "failed", error, logs: this.takeLogs() });
1470
+ this.enqueue({ status: "failed", error, logs: this.takeLogsForTerminal() });
1471
+ }
1472
+ /**
1473
+ * Esvazia o buffer para o PATCH terminal respeitando o teto de 100 logs por PATCH:
1474
+ * o excedente sai antes, em PATCHes `running` na mesma cadeia (a ordem se mantém).
1475
+ */
1476
+ takeLogsForTerminal() {
1477
+ let logs = this.takeLogs();
1478
+ while (logs.length > MAX_LOGS_PER_PATCH) {
1479
+ this.enqueue({ status: "running", logs: logs.slice(0, MAX_LOGS_PER_PATCH) });
1480
+ logs = logs.slice(MAX_LOGS_PER_PATCH);
1481
+ }
1482
+ return logs;
1424
1483
  }
1425
1484
  // ── Progresso ────────────────────────────────────────────────────────────────
1426
1485
  /** Valida, clampa e marca o snapshot como pendente; agenda flush só se nada mais vai sair. */
@@ -1528,7 +1587,7 @@ var RunReporter = class {
1528
1587
  id: id ? truncate(String(id), 255) : uuid4(),
1529
1588
  seq: this.itemSeq++,
1530
1589
  status,
1531
- ...message ? { message: truncate(scrubSecrets(message), MAX_ITEM_MESSAGE_CHARS) } : {},
1590
+ ...message ? { message: truncate(scrubSecrets(safeString(message)), MAX_ITEM_MESSAGE_CHARS) } : {},
1532
1591
  ...scrubbedPayload ? { payload: scrubbedPayload } : {},
1533
1592
  ts: nowISO()
1534
1593
  });
@@ -1617,8 +1676,10 @@ var RunReporter = class {
1617
1676
  async attach(image, options = {}) {
1618
1677
  const id = uuid4();
1619
1678
  const level = options.level ?? "info";
1620
- const caption = options.caption ? truncate(scrubSecrets(options.caption), MAX_CAPTION_CHARS) : void 0;
1621
- const message = options.message ?? options.caption ?? "captura de tela";
1679
+ const rawCaption = options.caption !== void 0 ? safeString(options.caption) : void 0;
1680
+ const rawMessage = options.message !== void 0 ? safeString(options.message) : void 0;
1681
+ const caption = rawCaption ? truncate(scrubSecrets(rawCaption), MAX_CAPTION_CHARS) : void 0;
1682
+ const message = rawMessage ?? rawCaption ?? "captura de tela";
1622
1683
  if (this.dead) return { id, ok: false, reason: "canceled" };
1623
1684
  if (this.attachmentsUnsupported) {
1624
1685
  this.log(level, message);
@@ -2238,6 +2299,42 @@ var TaskAgent = class {
2238
2299
  }
2239
2300
  };
2240
2301
 
2302
+ // src/tasks/item-columns.ts
2303
+ var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
2304
+ var MAX_COLUMNS = 12;
2305
+ var MAX_LABEL_CHARS = 60;
2306
+ function normalizeItemColumns(slug, input, logger) {
2307
+ if (input === void 0 || input === null) return void 0;
2308
+ const columns = input.columns;
2309
+ if (typeof input !== "object" || !Array.isArray(columns)) {
2310
+ logger.warn(`task "${slug}": items.columns precisa ser uma lista \u2014 declara\xE7\xE3o de colunas ignorada`);
2311
+ return void 0;
2312
+ }
2313
+ const result = [];
2314
+ const seen = /* @__PURE__ */ new Set();
2315
+ for (const entry of columns) {
2316
+ const rawKey = typeof entry === "string" ? entry : entry?.key;
2317
+ const rawLabel = typeof entry === "object" && entry !== null ? entry.label : void 0;
2318
+ const key = typeof rawKey === "string" ? rawKey.trim() : "";
2319
+ if (!KEY_RE.test(key)) {
2320
+ logger.warn(`task "${slug}": coluna de item inv\xE1lida ignorada (${JSON.stringify(rawKey)}) \u2014 use letras, n\xFAmeros, ponto, h\xEDfen e underline`);
2321
+ continue;
2322
+ }
2323
+ if (seen.has(key)) {
2324
+ logger.warn(`task "${slug}": coluna de item "${key}" repetida ignorada`);
2325
+ continue;
2326
+ }
2327
+ if (result.length >= MAX_COLUMNS) {
2328
+ logger.warn(`task "${slug}": no m\xE1ximo ${MAX_COLUMNS} colunas de item \u2014 "${key}" em diante ignoradas`);
2329
+ break;
2330
+ }
2331
+ seen.add(key);
2332
+ const label = typeof rawLabel === "string" ? rawLabel.trim().slice(0, MAX_LABEL_CHARS) : "";
2333
+ result.push(label ? { key, label } : { key });
2334
+ }
2335
+ return result.length > 0 ? result : void 0;
2336
+ }
2337
+
2241
2338
  // src/tasks/local.ts
2242
2339
  var LocalRunner = class {
2243
2340
  constructor(task, deps) {
@@ -2316,6 +2413,15 @@ var TaskRegistry = class {
2316
2413
  allSlugs() {
2317
2414
  return [...this.tasks.keys()];
2318
2415
  }
2416
+ /** Colunas de itens declaradas no código, por slug (`instance.task_items`); undefined se nenhuma task declarou. */
2417
+ itemDeclarations() {
2418
+ let result;
2419
+ for (const task of this.tasks.values()) {
2420
+ if (!task.options.itemColumns) continue;
2421
+ (result ??= {})[task.slug] = { attributes: task.options.itemColumns };
2422
+ }
2423
+ return result;
2424
+ }
2319
2425
  clear() {
2320
2426
  this.tasks.clear();
2321
2427
  }
@@ -2469,8 +2575,9 @@ function ensureRuntime(options) {
2469
2575
  }
2470
2576
  const config = resolveConfig({ clientSecret: options.clientSecret });
2471
2577
  const logger = makeLogger(options.debug ?? false);
2578
+ const environment = resolveEnvironmentOption(options.environment);
2472
2579
  const client = new Client(
2473
- { environment: options.environment, release: options.release, tags: options.tags, debug: options.debug },
2580
+ { environment, release: options.release, tags: options.tags, debug: options.debug },
2474
2581
  config
2475
2582
  );
2476
2583
  const api = new TasksApi({ baseUrl: config.tasksBaseUrl, key: config.key, logger });
@@ -2487,6 +2594,7 @@ function ensureRuntime(options) {
2487
2594
  agent: void 0,
2488
2595
  locals: /* @__PURE__ */ new Map(),
2489
2596
  instanceId: resolveInstanceId(options.instanceId),
2597
+ environment,
2490
2598
  pollIntervalMs: void 0,
2491
2599
  booted: false,
2492
2600
  bootScheduled: false,
@@ -2511,7 +2619,7 @@ function clampConcurrency(value) {
2511
2619
  if (value === void 0 || !Number.isFinite(value)) return 1;
2512
2620
  return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(value)));
2513
2621
  }
2514
- function resolveTaskOptions(options, mode) {
2622
+ function resolveTaskOptions(slug, options, mode, logger) {
2515
2623
  const retries = options.retries === void 0 || !Number.isFinite(options.retries) ? DEFAULT_RETRIES2 : Math.max(0, Math.floor(options.retries));
2516
2624
  return {
2517
2625
  retries,
@@ -2520,7 +2628,8 @@ function resolveTaskOptions(options, mode) {
2520
2628
  concurrency: mode === "remote" ? clampConcurrency(options.concurrency) : 1,
2521
2629
  logFlushIntervalMs: options.logFlushIntervalMs,
2522
2630
  cancelCheckIntervalMs: options.cancelCheckIntervalMs,
2523
- cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS
2631
+ cancelGraceMs: options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS,
2632
+ itemColumns: normalizeItemColumns(slug, options.items, logger)
2524
2633
  };
2525
2634
  }
2526
2635
  function instanceInfo(rt) {
@@ -2529,9 +2638,11 @@ function instanceInfo(rt) {
2529
2638
  version: SDK_VERSION,
2530
2639
  host: hostname(),
2531
2640
  // ambiente da instância: junto do heartbeat, é como o servidor sabe onde o robô roda
2532
- environment: rt.options.environment,
2641
+ environment: rt.environment,
2533
2642
  // TODAS as tasks (register + start) — o painel mostra o que o agente sabe fazer
2534
2643
  tasks: rt.registry.allSlugs(),
2644
+ // colunas da aba "itens" declaradas no código — a plataforma usa enquanto o usuário não personalizar
2645
+ task_items: rt.registry.itemDeclarations(),
2535
2646
  // o servidor calcula a tolerância de offline (3×) a partir disto em agentes que fazem
2536
2647
  // poll; clampado à faixa aceita pelo schema (1s..15min)
2537
2648
  poll_interval_ms: rt.agent ? Math.min(Math.max(rt.agent.pollIntervalMs, 1e3), 9e5) : void 0,
@@ -2600,7 +2711,7 @@ function addTask(fn, mode, task, handler, options) {
2600
2711
  rt.logger.warn('op\xE7\xE3o "pollIntervalMs" divergente ignorada \u2014 o primeiro register() define o intervalo do poll');
2601
2712
  }
2602
2713
  }
2603
- const registered = { slug, mode, handler, options: resolveTaskOptions(options, mode) };
2714
+ const registered = { slug, mode, handler, options: resolveTaskOptions(slug, options, mode, rt.logger) };
2604
2715
  rt.registry.add(registered);
2605
2716
  if (!rt.booted) {
2606
2717
  scheduleBoot(rt);
@@ -2689,11 +2800,11 @@ function warnOutsideRun(member) {
2689
2800
  }
2690
2801
  var FALLBACK_TASK = "hobots";
2691
2802
  var fallbackLog = {
2692
- debug: (message) => printRunLog(FALLBACK_TASK, "debug", message),
2693
- info: (message) => printRunLog(FALLBACK_TASK, "info", message),
2694
- warn: (message) => printRunLog(FALLBACK_TASK, "warning", message),
2695
- error: (message) => printRunLog(FALLBACK_TASK, "error", message),
2696
- success: (message) => printRunLog(FALLBACK_TASK, "success", message)
2803
+ debug: (message, ...rest) => printRunLog(FALLBACK_TASK, "debug", joinLogArgs(message, rest)),
2804
+ info: (message, ...rest) => printRunLog(FALLBACK_TASK, "info", joinLogArgs(message, rest)),
2805
+ warn: (message, ...rest) => printRunLog(FALLBACK_TASK, "warning", joinLogArgs(message, rest)),
2806
+ error: (message, ...rest) => printRunLog(FALLBACK_TASK, "error", joinLogArgs(message, rest)),
2807
+ success: (message, ...rest) => printRunLog(FALLBACK_TASK, "success", joinLogArgs(message, rest))
2697
2808
  };
2698
2809
  var noop = () => void 0;
2699
2810
  var fallbackItems = { add: noop, succeeded: noop, failed: noop, occurrence: noop };