hobots 1.7.0 → 1.7.2

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
@@ -26,7 +26,7 @@ Também funciona em CommonJS: `const Hobots = require('hobots')`.
26
26
 
27
27
  ## Um SDK, duas capabilities, um `init()`
28
28
 
29
- O SDK expõe **duas capabilities** autenticadas pelo **mesmo Client Secret do projeto**, configuradas em um único `init()`:
29
+ O SDK expõe **duas capabilities** autenticadas pelo **mesmo Client Secret do agente**, configuradas em um único `init()`:
30
30
 
31
31
  | Capability | Como habilita | Para quê | Ciclo de vida |
32
32
  | --- | --- | --- | --- |
@@ -59,13 +59,13 @@ Hobots.start(); // inicia o polling de tasks (segura o processo vivo)
59
59
 
60
60
  ## O Client Secret
61
61
 
62
- Cada projeto tem **um único Client Secret** — a mesma credencial autentica a telemetria **e** as tasks:
62
+ Cada agente tem **um único Client Secret** — a mesma credencial autentica a telemetria **e** as tasks:
63
63
 
64
64
  ```
65
65
  hb_<64 hex>
66
66
  ```
67
67
 
68
- A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL. Copie o Client Secret no app, em **Configurações → Projetos** — os snippets de integração já vêm prontos. O SDK fala com a API oficial `https://api2.hobots.app` automaticamente; em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL` (ex.: `HOBOTS_API_URL=http://localhost:3001`).
68
+ A credencial é **secreta** e viaja sempre no header `Authorization: Bearer` — nunca na URL. Copie o Client Secret no app, em **Configurações → Agentes** — os snippets de integração já vêm prontos. O SDK fala com a API oficial `https://api2.hobots.app` automaticamente; em desenvolvimento, aponte para outro host com a env `HOBOTS_API_URL` (ex.: `HOBOTS_API_URL=http://localhost:3001`).
69
69
 
70
70
  ---
71
71
 
@@ -90,7 +90,7 @@ Hobots.init({
90
90
 
91
91
  | Opção | Tipo | Default | Descrição |
92
92
  | --- | --- | --- | --- |
93
- | `clientSecret` | `string` | **obrigatório** | o Client Secret do projeto (`hb_<64 hex>`), copiado em **Configurações → Projetos** |
93
+ | `clientSecret` | `string` | **obrigatório** | o Client Secret do agente (`hb_<64 hex>`), copiado em **Configurações → Agentes** |
94
94
  | `environment` | `string` | `'production'` | ambiente lógico |
95
95
  | `release` | `string` | — | versão do programa, ex. `robo-nfe@1.4.2` |
96
96
  | `instanceId` | `string` | **obrigatório** | identidade única da instância — heartbeat e poll de tasks convergem nela |
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 ? "2.0.0" : "0.0.0-dev";
166
+ var SDK_VERSION = true ? "1.7.1" : "0.0.0-dev";
167
167
  function isNode() {
168
168
  return typeof process !== "undefined" && typeof process.versions?.node === "string";
169
169
  }
@@ -1068,6 +1068,15 @@ var RunReporter = class {
1068
1068
  this.clearFlushTimer();
1069
1069
  this.buffer = [];
1070
1070
  }
1071
+ /**
1072
+ * Drena o buffer pendente para o PATCH único do exit path (chame ANTES de kill,
1073
+ * que descarta o buffer). Capado em um chunk — o exit path não tem retry nem fila,
1074
+ * então preserva os logs mais recentes, os mais valiosos na morte do processo.
1075
+ */
1076
+ drainLogs() {
1077
+ this.clearFlushTimer();
1078
+ return this.takeLogs().slice(-MAX_LOGS_PER_PATCH);
1079
+ }
1071
1080
  log(level, message) {
1072
1081
  if (this.dead) return;
1073
1082
  const scrubbed = scrubSecrets(message);
@@ -1160,10 +1169,16 @@ var RunReporter = class {
1160
1169
  return this.chain;
1161
1170
  }
1162
1171
  enqueue(update) {
1163
- if (this.dead) return;
1172
+ if (this.dead) {
1173
+ this.salvageLogs(update.logs);
1174
+ return;
1175
+ }
1164
1176
  this.pendingUpdates++;
1165
1177
  this.chain = this.chain.then(async () => {
1166
- if (this.dead) return;
1178
+ if (this.dead) {
1179
+ this.salvageLogs(update.logs);
1180
+ return;
1181
+ }
1167
1182
  const payload = update.logs && update.logs.length === 0 ? { ...update, logs: void 0 } : update;
1168
1183
  try {
1169
1184
  await this.api.updateRun(this.runId, payload);
@@ -1172,16 +1187,35 @@ var RunReporter = class {
1172
1187
  this.dead = true;
1173
1188
  this.stopCancelWatch();
1174
1189
  this.abort();
1175
- this.buffer = [];
1176
- this.logger.warn(`run ${this.runId} cancelada/removida no servidor \u2014 reporte suprimido`);
1190
+ const pending = this.buffer.splice(0, this.buffer.length);
1191
+ this.salvageLogs(pending);
1192
+ this.logger.warn(
1193
+ `run ${this.runId} cancelada/finalizada no servidor \u2014 status suprimido` + (pending.length > 0 ? `, ${pending.length} log(s) enviados como log tardio` : "")
1194
+ );
1177
1195
  return;
1178
1196
  }
1197
+ if (update.logs && update.logs.length > 0) {
1198
+ this.buffer.unshift(...update.logs);
1199
+ this.scheduleFlush();
1200
+ }
1179
1201
  this.logger.error(`update da run ${this.runId} perdido:`, error);
1180
1202
  }
1181
1203
  }).finally(() => {
1182
1204
  this.pendingUpdates--;
1183
1205
  });
1184
1206
  }
1207
+ /**
1208
+ * Best-effort de log tardio: envia logs para uma run já terminal, fora da fila.
1209
+ * O servidor anexa os logs mesmo respondendo 409; erros são engolidos — este é
1210
+ * o último recurso, não um canal confiável.
1211
+ */
1212
+ salvageLogs(logs) {
1213
+ if (!logs || logs.length === 0) return;
1214
+ for (let i = 0; i < logs.length; i += MAX_LOGS_PER_PATCH) {
1215
+ void this.api.updateRunOnce(this.runId, { status: "running", logs: logs.slice(i, i + MAX_LOGS_PER_PATCH) }).catch(() => {
1216
+ });
1217
+ }
1218
+ }
1185
1219
  };
1186
1220
 
1187
1221
  // src/tasks/run-context-store.ts
@@ -1337,9 +1371,14 @@ var TaskAgent = class {
1337
1371
  const pending = [];
1338
1372
  for (const reporter of this.reporters) {
1339
1373
  if (reporter.isDead) continue;
1374
+ const logs = reporter.drainLogs();
1340
1375
  reporter.kill();
1341
1376
  pending.push(
1342
- this.api.updateRunOnce(reporter.context.runId, { status: "failed", error: { type: "AgentTerminated", message } }, timeoutMs).catch(() => {
1377
+ this.api.updateRunOnce(
1378
+ reporter.context.runId,
1379
+ { status: "failed", error: { type: "AgentTerminated", message }, logs: logs.length > 0 ? logs : void 0 },
1380
+ timeoutMs
1381
+ ).catch(() => {
1343
1382
  })
1344
1383
  );
1345
1384
  }
@@ -1395,7 +1434,7 @@ var TaskAgent = class {
1395
1434
  if (error instanceof TasksApiError && error.status === 401) {
1396
1435
  this.consecutiveAuthFailures++;
1397
1436
  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");
1437
+ this.logger.error("chave rejeitada (401) \u2014 verifique o Client Secret do agente em Configura\xE7\xF5es \u2192 Agentes");
1399
1438
  }
1400
1439
  return Math.max(this.pollIntervalMs, AUTH_FAILURE_INTERVAL_MS);
1401
1440
  }