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 +89 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +89 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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.
|
|
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(
|
|
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
|
|
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);
|