hobots 1.7.1 → 1.7.3

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
@@ -93,11 +93,11 @@ function validateBaseUrl(raw, prefix) {
93
93
  function resolveConfig(options, prefix = "[hobots]") {
94
94
  const key = options.clientSecret?.trim();
95
95
  if (!key) {
96
- throw new Error(`${prefix} clientSecret ausente \u2014 copie o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos`);
96
+ throw new Error(`${prefix} clientSecret ausente \u2014 copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`);
97
97
  }
98
98
  if (!SECRET_PATTERN.test(key)) {
99
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`
100
+ `${prefix} clientSecret com formato inv\xE1lido (${key.length} caracteres) \u2014 esperado "hb_" + 64 hex; copie o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes`
101
101
  );
102
102
  }
103
103
  const baseUrl = validateBaseUrl(envBaseUrl() ?? DEFAULT_BASE_URL, prefix);
@@ -163,7 +163,7 @@ function scrubSecrets(text) {
163
163
 
164
164
  // src/utils.ts
165
165
  var SDK_NAME = "hobots";
166
- var SDK_VERSION = true ? "1.7.0" : "0.0.0-dev";
166
+ var SDK_VERSION = true ? "1.7.2" : "0.0.0-dev";
167
167
  function isNode() {
168
168
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
169
169
  }
@@ -722,12 +722,6 @@ var Scope = class _Scope {
722
722
  function spanId() {
723
723
  return uuid4().slice(0, 16);
724
724
  }
725
- function printSpanLog(kind, phase, label, startTs, endTs) {
726
- const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
727
- const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? "green" : "cyan");
728
- const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
729
- console.log(`${tag} ${label}${suffix}`);
730
- }
731
725
  function spanLabel(op, description) {
732
726
  return [op, description].filter(Boolean).join(" \u2014 ") || "(sem nome)";
733
727
  }
@@ -758,7 +752,7 @@ var Span = class {
758
752
  finish() {
759
753
  if (this.endTimestamp) return;
760
754
  this.endTimestamp = nowISO();
761
- printSpanLog("span", "finish", spanLabel(this.op, this.description), this.startTimestamp, this.endTimestamp);
755
+ this.transaction.emit("span", "finish", spanLabel(this.op, this.description), this.startTimestamp, this.endTimestamp);
762
756
  }
763
757
  toJSON() {
764
758
  return {
@@ -780,7 +774,7 @@ var Transaction = class {
780
774
  this.op = op;
781
775
  this.sampled = sampled;
782
776
  this.runId = runId;
783
- printSpanLog("transaction", "start", op ? `${name} (${op})` : name);
777
+ this.emit("transaction", "start", op ? `${name} (${op})` : name);
784
778
  }
785
779
  eventId = uuid4();
786
780
  spanId = spanId();
@@ -789,10 +783,36 @@ var Transaction = class {
789
783
  spans = [];
790
784
  tags = {};
791
785
  finished = false;
786
+ lifecycleSink;
787
+ // eventos emitidos antes do sink existir (o START da transaction sai no construtor,
788
+ // antes do RunReporter conectar) — replay no setLifecycleSink
789
+ lifecycleBacklog = [];
790
+ /**
791
+ * Ecoa no terminal o início/fim de um span ou transaction (sempre, independente de
792
+ * sampling ou do debug) e espelha a linha no sink de lifecycle, se conectado — é
793
+ * assim que os eventos de performance também aparecem nos logs da run no app.
794
+ */
795
+ emit(kind, phase, label, startTs, endTs) {
796
+ const durationMs = startTs && endTs ? new Date(endTs).getTime() - new Date(startTs).getTime() : void 0;
797
+ const tag = colorize(`[hobots:${kind}] ${phase.toUpperCase()}`, phase === "finish" ? "green" : "cyan");
798
+ const suffix = durationMs != null ? ` (${durationMs}ms)` : "";
799
+ console.log(`${tag} ${label}${suffix}`);
800
+ const level = phase === "finish" ? "success" : "info";
801
+ const message = `[${kind}] ${phase.toUpperCase()} ${label}${suffix}`;
802
+ if (this.lifecycleSink) this.lifecycleSink(level, message);
803
+ else if (this.lifecycleBacklog.length < 100) this.lifecycleBacklog.push([level, message]);
804
+ }
805
+ /** Conecta o espelho de lifecycle (RunReporter) e faz replay do backlog em ordem. */
806
+ setLifecycleSink(sink) {
807
+ this.lifecycleSink = sink;
808
+ const backlog = this.lifecycleBacklog;
809
+ this.lifecycleBacklog = [];
810
+ for (const [level, message] of backlog) sink(level, message);
811
+ }
792
812
  createSpan(parentSpanId, op, description) {
793
813
  const span = new Span(this, parentSpanId, op, description);
794
814
  this.spans.push(span);
795
- printSpanLog("span", "start", spanLabel(op, description));
815
+ this.emit("span", "start", spanLabel(op, description));
796
816
  return span;
797
817
  }
798
818
  /** Span direto da transaction. */
@@ -816,7 +836,7 @@ var Transaction = class {
816
836
  this.finished = true;
817
837
  for (const span of this.spans) span.finish();
818
838
  const endTimestamp = nowISO();
819
- printSpanLog(
839
+ this.emit(
820
840
  "transaction",
821
841
  "finish",
822
842
  this.op ? `${this.name} (${this.op})` : this.name,
@@ -1024,6 +1044,7 @@ var RunReporter = class {
1024
1044
  error: (message) => this.log("error", message),
1025
1045
  success: (message) => this.log("success", message)
1026
1046
  };
1047
+ tx.setLifecycleSink((level, message) => this.push(level, message));
1027
1048
  const signal = this.abortController.signal;
1028
1049
  this.context = {
1029
1050
  runId,
@@ -1068,10 +1089,25 @@ var RunReporter = class {
1068
1089
  this.clearFlushTimer();
1069
1090
  this.buffer = [];
1070
1091
  }
1092
+ /**
1093
+ * Drena o buffer pendente para o PATCH único do exit path (chame ANTES de kill,
1094
+ * que descarta o buffer). Capado em um chunk — o exit path não tem retry nem fila,
1095
+ * então preserva os logs mais recentes, os mais valiosos na morte do processo.
1096
+ */
1097
+ drainLogs() {
1098
+ this.clearFlushTimer();
1099
+ return this.takeLogs().slice(-MAX_LOGS_PER_PATCH);
1100
+ }
1071
1101
  log(level, message) {
1072
1102
  if (this.dead) return;
1073
1103
  const scrubbed = scrubSecrets(message);
1074
1104
  printRunLog(this.context.task, level, scrubbed);
1105
+ this.push(level, scrubbed, true);
1106
+ }
1107
+ /** Bufferiza uma entrada de log sem eco no terminal (cap, truncate, flush). */
1108
+ push(level, message, alreadyScrubbed = false) {
1109
+ if (this.dead) return;
1110
+ const scrubbed = alreadyScrubbed ? message : scrubSecrets(message);
1075
1111
  if (this.loggedCount >= MAX_LOGS_PER_RUN) {
1076
1112
  this.droppedCount++;
1077
1113
  return;
@@ -1160,10 +1196,16 @@ var RunReporter = class {
1160
1196
  return this.chain;
1161
1197
  }
1162
1198
  enqueue(update) {
1163
- if (this.dead) return;
1199
+ if (this.dead) {
1200
+ this.salvageLogs(update.logs);
1201
+ return;
1202
+ }
1164
1203
  this.pendingUpdates++;
1165
1204
  this.chain = this.chain.then(async () => {
1166
- if (this.dead) return;
1205
+ if (this.dead) {
1206
+ this.salvageLogs(update.logs);
1207
+ return;
1208
+ }
1167
1209
  const payload = update.logs && update.logs.length === 0 ? { ...update, logs: void 0 } : update;
1168
1210
  try {
1169
1211
  await this.api.updateRun(this.runId, payload);
@@ -1172,16 +1214,35 @@ var RunReporter = class {
1172
1214
  this.dead = true;
1173
1215
  this.stopCancelWatch();
1174
1216
  this.abort();
1175
- this.buffer = [];
1176
- this.logger.warn(`run ${this.runId} cancelada/removida no servidor \u2014 reporte suprimido`);
1217
+ const pending = this.buffer.splice(0, this.buffer.length);
1218
+ this.salvageLogs(pending);
1219
+ this.logger.warn(
1220
+ `run ${this.runId} cancelada/finalizada no servidor \u2014 status suprimido` + (pending.length > 0 ? `, ${pending.length} log(s) enviados como log tardio` : "")
1221
+ );
1177
1222
  return;
1178
1223
  }
1224
+ if (update.logs && update.logs.length > 0) {
1225
+ this.buffer.unshift(...update.logs);
1226
+ this.scheduleFlush();
1227
+ }
1179
1228
  this.logger.error(`update da run ${this.runId} perdido:`, error);
1180
1229
  }
1181
1230
  }).finally(() => {
1182
1231
  this.pendingUpdates--;
1183
1232
  });
1184
1233
  }
1234
+ /**
1235
+ * Best-effort de log tardio: envia logs para uma run já terminal, fora da fila.
1236
+ * O servidor anexa os logs mesmo respondendo 409; erros são engolidos — este é
1237
+ * o último recurso, não um canal confiável.
1238
+ */
1239
+ salvageLogs(logs) {
1240
+ if (!logs || logs.length === 0) return;
1241
+ for (let i = 0; i < logs.length; i += MAX_LOGS_PER_PATCH) {
1242
+ void this.api.updateRunOnce(this.runId, { status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) }).catch(() => {
1243
+ });
1244
+ }
1245
+ }
1185
1246
  };
1186
1247
 
1187
1248
  // src/tasks/run-context-store.ts
@@ -1337,9 +1398,14 @@ var TaskAgent = class {
1337
1398
  const pending = [];
1338
1399
  for (const reporter of this.reporters) {
1339
1400
  if (reporter.isDead) continue;
1401
+ const logs = reporter.drainLogs();
1340
1402
  reporter.kill();
1341
1403
  pending.push(
1342
- this.api.updateRunOnce(reporter.context.runId, { status: "failed", error: { type: "AgentTerminated", message } }, timeoutMs).catch(() => {
1404
+ this.api.updateRunOnce(
1405
+ reporter.context.runId,
1406
+ { status: "failed", error: { type: "AgentTerminated", message }, logs: logs.length > 0 ? logs : void 0 },
1407
+ timeoutMs
1408
+ ).catch(() => {
1343
1409
  })
1344
1410
  );
1345
1411
  }
@@ -1395,7 +1461,7 @@ var TaskAgent = class {
1395
1461
  if (error instanceof TasksApiError && error.status === 401) {
1396
1462
  this.consecutiveAuthFailures++;
1397
1463
  if (this.consecutiveAuthFailures === 1 || this.consecutiveAuthFailures % 10 === 0) {
1398
- this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do projeto em Configura\xE7\xF5es \u2192 Projetos");
1464
+ this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes");
1399
1465
  }
1400
1466
  return Math.max(this.pollIntervalMs, AUTH_FAILURE_INTERVAL_MS);
1401
1467
  }
@@ -1438,15 +1504,16 @@ var TaskAgent = class {
1438
1504
  this.logger.debug(`executando run ${run.id} (${run.task})`);
1439
1505
  try {
1440
1506
  await runWithRunContext({ runId: run.id, tx }, () => this.invokeWithTimeout(handler, run, reporter));
1441
- reporter.reportSuccess();
1442
1507
  tx.setStatus("ok");
1508
+ tx.finish();
1509
+ reporter.reportSuccess();
1443
1510
  this.logger.debug(`run ${run.id} conclu\xEDda`);
1444
1511
  } catch (error) {
1445
- reporter.reportFailure(toRunError(error));
1446
1512
  tx.setStatus("error");
1513
+ tx.finish();
1514
+ reporter.reportFailure(toRunError(error));
1447
1515
  this.logger.debug(`run ${run.id} falhou: ${String(error)}`);
1448
1516
  }
1449
- tx.finish();
1450
1517
  await reporter.finalize();
1451
1518
  } finally {
1452
1519
  this.reporters.delete(reporter);