hobots 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,6 +95,7 @@ Os dois podem coexistir no mesmo processo: um monitor sempre ligado + tasks sob
95
95
  | --- | --- |
96
96
  | `hobots.log.debug/info/warn/error/success(msg, ...rest)` | Log da execução, enviado incrementalmente ao app (e ecoado no terminal). Aceita vários argumentos como `console.log` — `hobots.log.info('nota', numero, 'ok')` — cada um vira texto, unidos por espaço. **Nunca lança** — pode ir em `finally`. |
97
97
  | `hobots.items.succeeded(payload?, { id }?)` · `.failed(msg, payload?, …)` · `.occurrence(msg, payload?, …)` · `.add({...})` | **A entrega do robô**: cada item processado, com status e atributos (viram colunas no app — declare-as com a opção `items: { columns: [...] }` do `register`/`start`). Bufferizado, em lotes. |
98
+ | `hobots.items.each(lista, async (el, item) => …, { id, payload }?)` | **Loop com o contrato garantido**: cada elemento vira step + item; exceção no handler = item `failed` com a mensagem e o loop **segue** até o fim; progresso automático. Só cancelamento/timeout interrompem. |
98
99
  | `hobots.progress.total(n)` · `.advance(k = 1)` · `.set(atual, total?)` · `.current` · `.snapshot` | Barra, percentual e tempo restante na solicitação. Sem `total()` nada é enviado. |
99
100
  | `hobots.transaction.startChild(op, descrição)` | Uma **step** no waterfall de performance da execução. A transaction da execução já existe e é finalizada pelo SDK. |
100
101
  | `hobots.attach(imagem, { caption?, level? })` | Pendura um **print** (PNG/JPEG/WEBP, até 5 MB, 20 por execução) na linha de log. Nunca lança nem bloqueia. |
@@ -0,0 +1,31 @@
1
+ // protocol-limits.json
2
+ var protocol_limits_default = {
3
+ MAX_ITEMS_PER_ENVELOPE: 50,
4
+ HEARTBEAT_DEFAULT_INTERVAL_MS: 3e4,
5
+ HEARTBEAT_MIN_INTERVAL_MS: 1e3,
6
+ HEARTBEAT_MAX_INTERVAL_MS: 9e5,
7
+ MAX_CLAIM_PER_POLL: 5,
8
+ MAX_LOG_MESSAGE_CHARS: 1e4,
9
+ MAX_LOGS_PER_PATCH: 100,
10
+ MAX_ERROR_MESSAGE_CHARS: 1e4,
11
+ MAX_ERROR_STACK_CHARS: 2e4,
12
+ MAX_ITEMS_PER_RUN: 1e4,
13
+ MAX_ITEMS_PER_REQUEST: 500,
14
+ MAX_ITEM_MESSAGE_CHARS: 2e3,
15
+ MAX_ITEM_PAYLOAD_BYTES: 16384,
16
+ MAX_ITEM_COLUMNS: 12,
17
+ MAX_ITEM_COLUMN_LABEL_CHARS: 60,
18
+ MAX_ATTACHMENT_BYTES: 5242880,
19
+ MAX_ATTACHMENTS_PER_RUN: 20,
20
+ MAX_ATTACHMENTS_PER_LOG_ENTRY: 4,
21
+ MAX_CAPTION_CHARS: 200,
22
+ MAX_IMAGE_DIMENSION_PX: 2e4
23
+ };
24
+
25
+ // src/wire-limits.ts
26
+ var WIRE_LIMITS = protocol_limits_default;
27
+
28
+ export {
29
+ WIRE_LIMITS
30
+ };
31
+ //# sourceMappingURL=chunk-5WIIACWC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../protocol-limits.json","../src/wire-limits.ts"],"sourcesContent":["{\n \"MAX_ITEMS_PER_ENVELOPE\": 50,\n \"HEARTBEAT_DEFAULT_INTERVAL_MS\": 30000,\n \"HEARTBEAT_MIN_INTERVAL_MS\": 1000,\n \"HEARTBEAT_MAX_INTERVAL_MS\": 900000,\n \"MAX_CLAIM_PER_POLL\": 5,\n \"MAX_LOG_MESSAGE_CHARS\": 10000,\n \"MAX_LOGS_PER_PATCH\": 100,\n \"MAX_ERROR_MESSAGE_CHARS\": 10000,\n \"MAX_ERROR_STACK_CHARS\": 20000,\n \"MAX_ITEMS_PER_RUN\": 10000,\n \"MAX_ITEMS_PER_REQUEST\": 500,\n \"MAX_ITEM_MESSAGE_CHARS\": 2000,\n \"MAX_ITEM_PAYLOAD_BYTES\": 16384,\n \"MAX_ITEM_COLUMNS\": 12,\n \"MAX_ITEM_COLUMN_LABEL_CHARS\": 60,\n \"MAX_ATTACHMENT_BYTES\": 5242880,\n \"MAX_ATTACHMENTS_PER_RUN\": 20,\n \"MAX_ATTACHMENTS_PER_LOG_ENTRY\": 4,\n \"MAX_CAPTION_CHARS\": 200,\n \"MAX_IMAGE_DIMENSION_PX\": 20000\n}\n","import limits from '../protocol-limits.json';\n\n/**\n * Limites do wire SDK ↔ servidor — fonte ÚNICA (protocol-limits.json): o SDK\n * Node lê daqui, a @hobots/api importa de `hobots/protocol` e o SDK Python\n * confere a paridade num teste que lê o mesmo JSON. Mudar um limite = mudar o\n * JSON (e, no Python, a constante espelhada — o teste acusa).\n */\nexport const WIRE_LIMITS = limits as Readonly<typeof limits>;\nexport type WireLimits = typeof WIRE_LIMITS;\n"],"mappings":";AAAA;AAAA,EACE,wBAA0B;AAAA,EAC1B,+BAAiC;AAAA,EACjC,2BAA6B;AAAA,EAC7B,2BAA6B;AAAA,EAC7B,oBAAsB;AAAA,EACtB,uBAAyB;AAAA,EACzB,oBAAsB;AAAA,EACtB,yBAA2B;AAAA,EAC3B,uBAAyB;AAAA,EACzB,mBAAqB;AAAA,EACrB,uBAAyB;AAAA,EACzB,wBAA0B;AAAA,EAC1B,wBAA0B;AAAA,EAC1B,kBAAoB;AAAA,EACpB,6BAA+B;AAAA,EAC/B,sBAAwB;AAAA,EACxB,yBAA2B;AAAA,EAC3B,+BAAiC;AAAA,EACjC,mBAAqB;AAAA,EACrB,wBAA0B;AAC5B;;;ACbO,IAAM,cAAc;","names":[]}
package/dist/index.cjs CHANGED
@@ -95,7 +95,7 @@ function scrubSecrets(text) {
95
95
 
96
96
  // src/utils.ts
97
97
  var SDK_NAME = "hobots";
98
- var SDK_VERSION = true ? "3.2.0" : "0.0.0-dev";
98
+ var SDK_VERSION = true ? "3.3.0" : "0.0.0-dev";
99
99
  function isNode() {
100
100
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
101
101
  }
@@ -269,12 +269,39 @@ function eventFromException(error, type = "error") {
269
269
  };
270
270
  }
271
271
 
272
+ // protocol-limits.json
273
+ var protocol_limits_default = {
274
+ MAX_ITEMS_PER_ENVELOPE: 50,
275
+ HEARTBEAT_DEFAULT_INTERVAL_MS: 3e4,
276
+ HEARTBEAT_MIN_INTERVAL_MS: 1e3,
277
+ HEARTBEAT_MAX_INTERVAL_MS: 9e5,
278
+ MAX_CLAIM_PER_POLL: 5,
279
+ MAX_LOG_MESSAGE_CHARS: 1e4,
280
+ MAX_LOGS_PER_PATCH: 100,
281
+ MAX_ERROR_MESSAGE_CHARS: 1e4,
282
+ MAX_ERROR_STACK_CHARS: 2e4,
283
+ MAX_ITEMS_PER_RUN: 1e4,
284
+ MAX_ITEMS_PER_REQUEST: 500,
285
+ MAX_ITEM_MESSAGE_CHARS: 2e3,
286
+ MAX_ITEM_PAYLOAD_BYTES: 16384,
287
+ MAX_ITEM_COLUMNS: 12,
288
+ MAX_ITEM_COLUMN_LABEL_CHARS: 60,
289
+ MAX_ATTACHMENT_BYTES: 5242880,
290
+ MAX_ATTACHMENTS_PER_RUN: 20,
291
+ MAX_ATTACHMENTS_PER_LOG_ENTRY: 4,
292
+ MAX_CAPTION_CHARS: 200,
293
+ MAX_IMAGE_DIMENSION_PX: 2e4
294
+ };
295
+
296
+ // src/wire-limits.ts
297
+ var WIRE_LIMITS = protocol_limits_default;
298
+
272
299
  // src/transport.ts
273
300
  var REQUEST_TIMEOUT_MS = 1e4;
274
301
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
275
302
  var DEFAULT_MAX_BATCH_SIZE = 10;
276
303
  var DEFAULT_MAX_QUEUE_SIZE = 200;
277
- var MAX_ITEMS_PER_ENVELOPE = 50;
304
+ var MAX_ITEMS_PER_ENVELOPE = WIRE_LIMITS.MAX_ITEMS_PER_ENVELOPE;
278
305
  var Transport = class {
279
306
  endpoint;
280
307
  key;
@@ -598,9 +625,9 @@ async function fallbackInstanceId() {
598
625
  }
599
626
 
600
627
  // src/heartbeat.ts
601
- var DEFAULT_INTERVAL_MS = 3e4;
602
- var MIN_REPORTED_INTERVAL_MS = 1e3;
603
- var MAX_REPORTED_INTERVAL_MS = 9e5;
628
+ var DEFAULT_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_DEFAULT_INTERVAL_MS;
629
+ var MIN_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MIN_INTERVAL_MS;
630
+ var MAX_REPORTED_INTERVAL_MS = WIRE_LIMITS.HEARTBEAT_MAX_INTERVAL_MS;
604
631
  var Heartbeat = class {
605
632
  constructor(client, options = {}, instanceId, sampler) {
606
633
  this.client = client;
@@ -611,6 +638,8 @@ var Heartbeat = class {
611
638
  this.metadata = options.metadata;
612
639
  this.keepAlive = options.keepAlive ?? false;
613
640
  }
641
+ client;
642
+ sampler;
614
643
  intervalMs;
615
644
  instanceId;
616
645
  name;
@@ -747,6 +776,8 @@ var MachineSampler = class {
747
776
  this.instanceId = instanceId;
748
777
  this.logger = logger;
749
778
  }
779
+ instanceId;
780
+ logger;
750
781
  os;
751
782
  fs;
752
783
  ready = false;
@@ -874,6 +905,7 @@ var TasksApiError = class extends Error {
874
905
  this.status = status;
875
906
  this.name = "TasksApiError";
876
907
  }
908
+ status;
877
909
  };
878
910
  async function withRequestTimeout(timeoutMs, external, send) {
879
911
  const { signal, cleanup } = withTimeoutSignal(timeoutMs, external);
@@ -1114,6 +1146,9 @@ var Backoff = class {
1114
1146
  this.label = label;
1115
1147
  this.floorMs = floorMs;
1116
1148
  }
1149
+ logger;
1150
+ label;
1151
+ floorMs;
1117
1152
  failures = 0;
1118
1153
  authFailures = 0;
1119
1154
  reset() {
@@ -1152,6 +1187,88 @@ function isTerminalError(error) {
1152
1187
  return error instanceof Error && TERMINAL_ERROR_NAMES.has(error.name);
1153
1188
  }
1154
1189
 
1190
+ // src/tasks/each.ts
1191
+ function thrownMessage(error) {
1192
+ if (error instanceof Error) return error.message || error.name;
1193
+ return safeString(error);
1194
+ }
1195
+ var ItemHandle = class {
1196
+ constructor(index, id, step, basePayload, sink) {
1197
+ this.index = index;
1198
+ this.id = id;
1199
+ this.step = step;
1200
+ this.basePayload = basePayload;
1201
+ this.sink = sink;
1202
+ }
1203
+ index;
1204
+ id;
1205
+ step;
1206
+ basePayload;
1207
+ sink;
1208
+ reported = false;
1209
+ status;
1210
+ succeeded(payload) {
1211
+ this.record("succeeded", void 0, payload);
1212
+ }
1213
+ failed(message, payload) {
1214
+ this.record("failed", message, payload);
1215
+ if (this.step && this.step.status !== "error") this.step.setStatus("error", message);
1216
+ }
1217
+ occurrence(message, payload) {
1218
+ this.record("occurrence", message, payload);
1219
+ if (this.step && this.step.status === "ok") this.step.setStatus("warning", message);
1220
+ }
1221
+ record(status, message, payload) {
1222
+ this.reported = true;
1223
+ this.status = status;
1224
+ const merged = { ...this.basePayload ?? {}, ...payload ?? {} };
1225
+ this.sink.report(status, message, Object.keys(merged).length > 0 ? merged : void 0, this.id);
1226
+ }
1227
+ };
1228
+ function resolveId(options, element, index) {
1229
+ const raw = options.id?.(element, index);
1230
+ if (raw === null || raw === void 0) return void 0;
1231
+ const text = safeString(raw);
1232
+ return text.length > 0 ? text : void 0;
1233
+ }
1234
+ function knownLength(list) {
1235
+ const length = list?.length;
1236
+ return typeof length === "number" && Number.isInteger(length) && length >= 1 ? length : void 0;
1237
+ }
1238
+ async function eachItem(list, handler, options, sink) {
1239
+ const result = { total: 0, succeeded: 0, failed: 0, occurrence: 0 };
1240
+ const length = options.progress === false ? void 0 : knownLength(list);
1241
+ if (length !== void 0) sink.declareTotal(length);
1242
+ let index = 0;
1243
+ for await (const element of list) {
1244
+ sink.checkCanceled();
1245
+ const id = resolveId(options, element, index);
1246
+ const description = options.description?.(element, index) ?? id ?? `#${index + 1}`;
1247
+ const step = sink.startStep(options.op ?? "item", description);
1248
+ const handle = new ItemHandle(index, id, step, options.payload?.(element, index), sink);
1249
+ try {
1250
+ await handler(element, handle, index);
1251
+ if (!handle.reported) handle.succeeded();
1252
+ } catch (error) {
1253
+ if (isTerminalError(error) || sink.isAborted()) {
1254
+ step?.setStatus("cancelled");
1255
+ step?.finish();
1256
+ throw error;
1257
+ }
1258
+ const message = thrownMessage(error);
1259
+ if (!handle.reported) handle.failed(message);
1260
+ step?.setError(error);
1261
+ sink.logError(`item ${description} falhou: ${message}`);
1262
+ }
1263
+ step?.finish();
1264
+ if (options.progress !== false) sink.advance();
1265
+ result.total += 1;
1266
+ result[handle.status ?? "succeeded"] += 1;
1267
+ index += 1;
1268
+ }
1269
+ return result;
1270
+ }
1271
+
1155
1272
  // src/retry.ts
1156
1273
  var DEFAULT_RETRIES = 3;
1157
1274
  var DEFAULT_DELAY_MS = 1e3;
@@ -1179,10 +1296,11 @@ async function retryWithBackoff(fn, options = {}) {
1179
1296
  }
1180
1297
 
1181
1298
  // src/tasks/attachments.ts
1182
- var MAX_ATTACHMENT_BYTES = 5242880;
1183
- var MAX_ATTACHMENTS_PER_RUN = 20;
1299
+ var MAX_ATTACHMENT_BYTES = WIRE_LIMITS.MAX_ATTACHMENT_BYTES;
1300
+ var MAX_ATTACHMENTS_PER_RUN = WIRE_LIMITS.MAX_ATTACHMENTS_PER_RUN;
1301
+ var MAX_ATTACHMENTS_PER_LOG_ENTRY = WIRE_LIMITS.MAX_ATTACHMENTS_PER_LOG_ENTRY;
1184
1302
  var MAX_CONCURRENT_UPLOADS = 3;
1185
- var MAX_CAPTION_CHARS = 200;
1303
+ var MAX_CAPTION_CHARS = WIRE_LIMITS.MAX_CAPTION_CHARS;
1186
1304
  var ATTACHMENT_DRAIN_MS = 15e3;
1187
1305
  async function readImageFile(path) {
1188
1306
  if (!isNode()) {
@@ -1196,7 +1314,7 @@ function startsWith(bytes, signature, offset = 0) {
1196
1314
  return signature.every((byte, index) => bytes[offset + index] === byte);
1197
1315
  }
1198
1316
  var PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10];
1199
- var MAX_PNG_DIMENSION = 2e4;
1317
+ var MAX_PNG_DIMENSION = WIRE_LIMITS.MAX_IMAGE_DIMENSION_PX;
1200
1318
  var JPEG_MAGIC = [255, 216, 255];
1201
1319
  var RIFF_MAGIC = [82, 73, 70, 70];
1202
1320
  var WEBP_MAGIC = [87, 69, 66, 80];
@@ -1259,13 +1377,13 @@ var DEFAULT_LOG_FLUSH_INTERVAL_MS = 2e3;
1259
1377
  var DEFAULT_CANCEL_CHECK_INTERVAL_MS = 5e3;
1260
1378
  var MIN_CANCEL_CHECK_INTERVAL_MS = 1e3;
1261
1379
  var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
1262
- var MAX_LOG_MESSAGE_CHARS = 1e4;
1263
- var MAX_LOGS_PER_PATCH = 100;
1380
+ var MAX_LOG_MESSAGE_CHARS = WIRE_LIMITS.MAX_LOG_MESSAGE_CHARS;
1381
+ var MAX_LOGS_PER_PATCH = WIRE_LIMITS.MAX_LOGS_PER_PATCH;
1264
1382
  var FLUSH_AT_BUFFERED = 20;
1265
- var MAX_ITEMS_PER_RUN = 1e4;
1266
- var MAX_ITEMS_PER_REQUEST = 500;
1267
- var MAX_ITEM_MESSAGE_CHARS = 2e3;
1268
- var MAX_ITEM_PAYLOAD_BYTES = 16384;
1383
+ var MAX_ITEMS_PER_RUN = WIRE_LIMITS.MAX_ITEMS_PER_RUN;
1384
+ var MAX_ITEMS_PER_REQUEST = WIRE_LIMITS.MAX_ITEMS_PER_REQUEST;
1385
+ var MAX_ITEM_MESSAGE_CHARS = WIRE_LIMITS.MAX_ITEM_MESSAGE_CHARS;
1386
+ var MAX_ITEM_PAYLOAD_BYTES = WIRE_LIMITS.MAX_ITEM_PAYLOAD_BYTES;
1269
1387
  var ITEMS_FLUSH_AT_BUFFERED = 100;
1270
1388
  var ITEMS_DRAIN_MS = 15e3;
1271
1389
  var PROGRESS_FLUSH_MS = 1e3;
@@ -1299,6 +1417,17 @@ var RunReporter = class {
1299
1417
  tx.setLifecycleSink((level, message) => this.push(level, message));
1300
1418
  const checkCanceled = () => this.checkCanceled();
1301
1419
  tx.setStepGuard(checkCanceled);
1420
+ const eachSink = {
1421
+ report: (status, message, payload, id) => this.addItem(status, message, payload, id),
1422
+ startStep: (op, description) => tx.startChild(op, description),
1423
+ declareTotal: (total) => {
1424
+ if (this.progressTotal == null) this.setProgress(this.progressCurrent, total);
1425
+ },
1426
+ advance: () => this.setProgress(this.progressCurrent + 1, this.progressTotal),
1427
+ checkCanceled,
1428
+ isAborted: () => this.abortController.signal.aborted,
1429
+ logError: (message) => this.log("error", message)
1430
+ };
1302
1431
  const items = {
1303
1432
  add: (item) => {
1304
1433
  checkCanceled();
@@ -1315,7 +1444,8 @@ var RunReporter = class {
1315
1444
  occurrence: (message, payload, options) => {
1316
1445
  checkCanceled();
1317
1446
  this.addItem("occurrence", message, payload, options?.id);
1318
- }
1447
+ },
1448
+ each: (list, handler, options = {}) => eachItem(list, handler, options, eachSink)
1319
1449
  };
1320
1450
  const progress = Object.defineProperties(
1321
1451
  {
@@ -1358,6 +1488,9 @@ var RunReporter = class {
1358
1488
  retry: (fn, options) => retryWithBackoff(fn, { ...options, signal, wait: sleep2 })
1359
1489
  };
1360
1490
  }
1491
+ api;
1492
+ runId;
1493
+ logger;
1361
1494
  context;
1362
1495
  buffer = [];
1363
1496
  dead = false;
@@ -1457,7 +1590,7 @@ var RunReporter = class {
1457
1590
  ts,
1458
1591
  level,
1459
1592
  message: truncate(scrubbed, MAX_LOG_MESSAGE_CHARS),
1460
- ...attachments && attachments.length > 0 ? { attachments } : {}
1593
+ ...attachments && attachments.length > 0 ? { attachments: attachments.slice(0, WIRE_LIMITS.MAX_ATTACHMENTS_PER_LOG_ENTRY) } : {}
1461
1594
  });
1462
1595
  if (this.buffer.length >= FLUSH_AT_BUFFERED) {
1463
1596
  this.flushLogs();
@@ -1682,6 +1815,7 @@ var RunReporter = class {
1682
1815
  }
1683
1816
  if (error instanceof TasksApiError) {
1684
1817
  this.logger.error(`lote de ${batch.length} item(ns) rejeitado pelo servidor:`, error);
1818
+ this.log("warning", `${batch.length} item(ns) descartado(s): envio rejeitado pelo servidor (HTTP ${error.status})`);
1685
1819
  return;
1686
1820
  }
1687
1821
  this.itemsBuffer.unshift(...batch);
@@ -1908,6 +2042,10 @@ function currentRunReporter() {
1908
2042
  }
1909
2043
 
1910
2044
  // src/transaction.ts
2045
+ var MAX_STEP_MESSAGE_CHARS = 2e3;
2046
+ function stepMessage(text) {
2047
+ return truncate(scrubSecrets(text), MAX_STEP_MESSAGE_CHARS);
2048
+ }
1911
2049
  function stepId() {
1912
2050
  return uuid4().slice(0, 16);
1913
2051
  }
@@ -1945,15 +2083,41 @@ var Step = class {
1945
2083
  this.op = op;
1946
2084
  this.description = description;
1947
2085
  }
2086
+ transaction;
2087
+ parentStepId;
2088
+ op;
2089
+ description;
1948
2090
  stepId = stepId();
1949
2091
  startTimestamp = nowISO();
1950
2092
  status = "ok";
1951
2093
  endTimestamp;
1952
2094
  data;
1953
- setStatus(status) {
2095
+ /**
2096
+ * Desfecho da step. `message` (opcional) é o motivo legível — vai em `data.message`
2097
+ * e aparece na Central (performance e banner da execução) e no log de FINISH.
2098
+ */
2099
+ setStatus(status, message) {
1954
2100
  this.status = status;
2101
+ if (message) this.setData("message", stepMessage(message));
2102
+ return this;
2103
+ }
2104
+ /**
2105
+ * Marca a step como `error` guardando o motivo em `data.error` ({ type, message }).
2106
+ * Aceita qualquer valor lançado; `message` sobrescreve a mensagem do erro.
2107
+ */
2108
+ setError(error, message) {
2109
+ this.status = "error";
2110
+ const type = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Error" : void 0;
2111
+ const raw = message ?? (error instanceof Error ? error.message || error.name : String(error));
2112
+ this.setData("error", { ...type ? { type } : {}, message: stepMessage(raw) });
1955
2113
  return this;
1956
2114
  }
2115
+ /** Motivo registrado via setStatus(…, message) ou setError — usado no log de FINISH. */
2116
+ reason() {
2117
+ const error = this.data?.error;
2118
+ const text = typeof error?.message === "string" ? error.message : this.data?.message;
2119
+ return typeof text === "string" && text ? text : void 0;
2120
+ }
1957
2121
  setData(key, value) {
1958
2122
  this.data = { ...this.data, [key]: value };
1959
2123
  return this;
@@ -1971,7 +2135,8 @@ var Step = class {
1971
2135
  stepLabel(this.op, this.description),
1972
2136
  this.startTimestamp,
1973
2137
  this.endTimestamp,
1974
- this.status
2138
+ this.status,
2139
+ this.reason()
1975
2140
  );
1976
2141
  }
1977
2142
  toJSON() {
@@ -1995,6 +2160,10 @@ var Transaction = class {
1995
2160
  this.runId = runId;
1996
2161
  this.emit("transaction", "start", op ? `${name} (${op})` : name);
1997
2162
  }
2163
+ client;
2164
+ name;
2165
+ op;
2166
+ runId;
1998
2167
  eventId = uuid4();
1999
2168
  stepId = stepId();
2000
2169
  startTimestamp = nowISO();
@@ -2014,9 +2183,9 @@ var Transaction = class {
2014
2183
  * logs da run no app), e ecoa no terminal apenas com `debug: true` — o SDK não
2015
2184
  * escreve no stdout do processo do usuário sem permissão.
2016
2185
  */
2017
- emit(kind, phase, label, startTs, endTs, status) {
2186
+ emit(kind, phase, label, startTs, endTs, status, reason) {
2018
2187
  const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
2019
- const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
2188
+ const suffix = (durationMs != null ? ` (${durationMs}ms)` : "") + (reason ? `: ${reason}` : "");
2020
2189
  if (this.client?.debug) {
2021
2190
  const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? colorForStatus(status ?? "ok") : "cyan");
2022
2191
  console.log(`${tag} ${label}${suffix}`);
@@ -2122,6 +2291,7 @@ async function executeRun(input) {
2122
2291
  await runWithRunContext({ reporter }, () => invokeWithRetry(task, run, reporter, logger));
2123
2292
  await reporter.finalizeAttachments();
2124
2293
  await reporter.finalizeItems();
2294
+ warnIncompleteProgress(reporter);
2125
2295
  tx.setStatus("ok");
2126
2296
  tx.finish();
2127
2297
  reporter.reportSuccess();
@@ -2139,6 +2309,13 @@ async function executeRun(input) {
2139
2309
  tracker.remove(reporter);
2140
2310
  }
2141
2311
  }
2312
+ function warnIncompleteProgress(reporter) {
2313
+ const snapshot = reporter.context.progress.snapshot;
2314
+ if (!snapshot || snapshot.current >= snapshot.total) return;
2315
+ reporter.context.log.warn(
2316
+ `execu\xE7\xE3o conclu\xEDda com progresso incompleto: ${snapshot.current}/${snapshot.total} \u2014 o handler terminou sem processar (ou sem avan\xE7ar) todos os itens previstos`
2317
+ );
2318
+ }
2142
2319
  function invokeWithRetry(task, run, reporter, logger) {
2143
2320
  const { retries, retryDelayMs } = task.options;
2144
2321
  const total = retries + 1;
@@ -2193,8 +2370,8 @@ function waitForCancelGrace(signal, graceMs) {
2193
2370
  signal.addEventListener("abort", () => setTimeout(resolve2, graceMs), { once: true });
2194
2371
  });
2195
2372
  }
2196
- var MAX_ERROR_MESSAGE_CHARS = 1e4;
2197
- var MAX_ERROR_STACK_CHARS = 2e4;
2373
+ var MAX_ERROR_MESSAGE_CHARS = WIRE_LIMITS.MAX_ERROR_MESSAGE_CHARS;
2374
+ var MAX_ERROR_STACK_CHARS = WIRE_LIMITS.MAX_ERROR_STACK_CHARS;
2198
2375
  function toRunError(error) {
2199
2376
  if (error instanceof Error) {
2200
2377
  return {
@@ -2209,7 +2386,7 @@ function toRunError(error) {
2209
2386
  // src/tasks/agent.ts
2210
2387
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
2211
2388
  var MIN_POLL_INTERVAL_MS = 1e3;
2212
- var MAX_CLAIM_PER_POLL = 5;
2389
+ var MAX_CLAIM_PER_POLL = WIRE_LIMITS.MAX_CLAIM_PER_POLL;
2213
2390
  var TaskAgent = class {
2214
2391
  constructor(options) {
2215
2392
  this.options = options;
@@ -2221,6 +2398,7 @@ var TaskAgent = class {
2221
2398
  this.pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
2222
2399
  this.backoff = new Backoff(this.logger, "poll", this.pollIntervalMs);
2223
2400
  }
2401
+ options;
2224
2402
  logger;
2225
2403
  pollIntervalMs;
2226
2404
  api;
@@ -2343,8 +2521,8 @@ var TaskAgent = class {
2343
2521
 
2344
2522
  // src/tasks/item-columns.ts
2345
2523
  var KEY_RE = /^[a-zA-Z0-9_.-]{1,100}$/;
2346
- var MAX_COLUMNS = 12;
2347
- var MAX_LABEL_CHARS = 60;
2524
+ var MAX_COLUMNS = WIRE_LIMITS.MAX_ITEM_COLUMNS;
2525
+ var MAX_LABEL_CHARS = WIRE_LIMITS.MAX_ITEM_COLUMN_LABEL_CHARS;
2348
2526
  function normalizeItemColumns(slug, input, logger) {
2349
2527
  if (input === void 0 || input === null) return void 0;
2350
2528
  const columns = input.columns;
@@ -2387,6 +2565,8 @@ var LocalRunner = class {
2387
2565
  this.resolveDone = resolve2;
2388
2566
  });
2389
2567
  }
2568
+ task;
2569
+ deps;
2390
2570
  abort = new AbortController();
2391
2571
  backoff;
2392
2572
  /** Resolve quando a run terminou (ou o runner foi parado antes de abri-la). */
@@ -2472,6 +2652,7 @@ var RunTracker = class {
2472
2652
  constructor(api) {
2473
2653
  this.api = api;
2474
2654
  }
2655
+ api;
2475
2656
  reporters = /* @__PURE__ */ new Set();
2476
2657
  waiters = /* @__PURE__ */ new Set();
2477
2658
  add(reporter) {
@@ -2849,7 +3030,22 @@ var fallbackLog = {
2849
3030
  success: (message, ...rest) => printRunLog(FALLBACK_TASK, "success", joinLogArgs(message, rest))
2850
3031
  };
2851
3032
  var noop = () => void 0;
2852
- var fallbackItems = { add: noop, succeeded: noop, failed: noop, occurrence: noop };
3033
+ var fallbackEachSink = {
3034
+ report: noop,
3035
+ startStep: () => void 0,
3036
+ declareTotal: noop,
3037
+ advance: noop,
3038
+ checkCanceled: noop,
3039
+ isAborted: () => false,
3040
+ logError: (message) => printRunLog(FALLBACK_TASK, "error", message)
3041
+ };
3042
+ var fallbackItems = {
3043
+ add: noop,
3044
+ succeeded: noop,
3045
+ failed: noop,
3046
+ occurrence: noop,
3047
+ each: (list, handler, options = {}) => eachItem(list, handler, options, fallbackEachSink)
3048
+ };
2853
3049
  var fallbackProgress = { total: noop, advance: noop, set: noop, current: 0, snapshot: null };
2854
3050
  var neverAborts = new AbortController().signal;
2855
3051
  var detachedTransaction;