hobots 1.3.2 → 1.3.4
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 +5 -5
- package/dist/index.cjs +218 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -4
- package/dist/index.d.ts +45 -4
- package/dist/index.js +218 -22
- package/dist/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/api-reference.md +4 -0
- package/docs/getting-started.md +1 -1
- package/docs/manager.md +23 -11
- package/docs/protocol.md +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Hobots.init({
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
51
|
-
ctx.log('
|
|
51
|
+
ctx.log.info('começando…');
|
|
52
52
|
return { processadas: 12 };
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -235,9 +235,9 @@ Hobots.init({
|
|
|
235
235
|
});
|
|
236
236
|
|
|
237
237
|
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
238
|
-
ctx.log(
|
|
238
|
+
ctx.log.info(`processando lote ${params.lote}…`);
|
|
239
239
|
if (ctx.signal.aborted) return; // cancelamento cooperativo
|
|
240
|
-
ctx.log('
|
|
240
|
+
ctx.log.info('12 notas processadas');
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
Hobots.start(); // começa o polling e MANTÉM O PROCESSO VIVO
|
|
@@ -277,7 +277,7 @@ interface RunContext {
|
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
- **`params`** — os dados do formulário configurado no app (JSON validado). Tipável: `Hobots.register<{ lote: number }>(...)`.
|
|
280
|
-
- **`ctx.log(
|
|
280
|
+
- **`ctx.log.<level>(message)`** — logs enviados **incrementalmente** ao app (PATCHes com throttle), visíveis na run quase em tempo real.
|
|
281
281
|
- **`ctx.signal`** — `AbortSignal` que dispara quando a run é **cancelada no app**, em **timeout** ou no **shutdown**.
|
|
282
282
|
- **Retorno** — **ignorado**: o SDK não captura nem envia o valor retornado pelo handler (resultados podem conter dados sensíveis e não são coletados).
|
|
283
283
|
- **Erro lançado** — a run é reportada como `failed` (mensagem cap 10k, stack 20k).
|
|
@@ -292,7 +292,7 @@ Uma run cancelada no app **não mata** o handler — o SDK apenas dispara `ctx.s
|
|
|
292
292
|
Hobots.register('processar-nfe', async (params, ctx) => {
|
|
293
293
|
for (const nota of params.notas) {
|
|
294
294
|
if (ctx.signal.aborted) {
|
|
295
|
-
ctx.log('
|
|
295
|
+
ctx.log.warn('cancelamento detectado — parando');
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
await processar(nota);
|
package/dist/index.cjs
CHANGED
|
@@ -111,7 +111,7 @@ function parseStacktrace(stack) {
|
|
|
111
111
|
|
|
112
112
|
// src/utils.ts
|
|
113
113
|
var SDK_NAME = "hobots";
|
|
114
|
-
var SDK_VERSION = true ? "1.3.
|
|
114
|
+
var SDK_VERSION = true ? "1.3.3" : "0.0.0-dev";
|
|
115
115
|
function isNode() {
|
|
116
116
|
return typeof process !== "undefined" && typeof process.versions?.node === "string";
|
|
117
117
|
}
|
|
@@ -433,28 +433,61 @@ var Heartbeat = class {
|
|
|
433
433
|
}
|
|
434
434
|
};
|
|
435
435
|
|
|
436
|
+
// src/internal-handlers.ts
|
|
437
|
+
var INTERNAL = /* @__PURE__ */ Symbol.for("hobots.sdk.internalListener");
|
|
438
|
+
function markInternal(listener) {
|
|
439
|
+
listener[INTERNAL] = true;
|
|
440
|
+
return listener;
|
|
441
|
+
}
|
|
442
|
+
function externalListenerCount(event) {
|
|
443
|
+
const listeners = process.listeners(event);
|
|
444
|
+
return listeners.filter((listener) => !listener[INTERNAL]).length;
|
|
445
|
+
}
|
|
446
|
+
var emergencyTasks = [];
|
|
447
|
+
function addEmergencyTask(task) {
|
|
448
|
+
emergencyTasks.push(task);
|
|
449
|
+
}
|
|
450
|
+
async function awaitEmergencyTasks(capMs) {
|
|
451
|
+
if (emergencyTasks.length === 0) return;
|
|
452
|
+
await Promise.race([Promise.allSettled(emergencyTasks), sleep(capMs)]);
|
|
453
|
+
}
|
|
454
|
+
|
|
436
455
|
// src/instrument.ts
|
|
456
|
+
var EMERGENCY_REPORT_CAP_MS = 3e3;
|
|
437
457
|
var installed = false;
|
|
458
|
+
function hasInstalledGlobalHandlers() {
|
|
459
|
+
return installed;
|
|
460
|
+
}
|
|
438
461
|
function installGlobalHandlers(client2, getScope) {
|
|
439
462
|
if (installed) return;
|
|
440
463
|
installed = true;
|
|
441
464
|
if (isNode()) {
|
|
442
|
-
process.on(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
process.on(
|
|
456
|
-
|
|
457
|
-
|
|
465
|
+
process.on(
|
|
466
|
+
"uncaughtException",
|
|
467
|
+
markInternal((error) => {
|
|
468
|
+
client2.captureException(error, { level: "fatal", tags: { mechanism: "uncaughtException" } }, getScope());
|
|
469
|
+
void client2.flush(2e3).then(async () => {
|
|
470
|
+
if (externalListenerCount("uncaughtException") === 0) {
|
|
471
|
+
await awaitEmergencyTasks(EMERGENCY_REPORT_CAP_MS);
|
|
472
|
+
console.error(error);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
})
|
|
477
|
+
);
|
|
478
|
+
process.on(
|
|
479
|
+
"unhandledRejection",
|
|
480
|
+
markInternal((reason) => {
|
|
481
|
+
client2.captureException(reason, { level: "error", tags: { mechanism: "unhandledRejection" } }, getScope());
|
|
482
|
+
void client2.flush(2e3);
|
|
483
|
+
})
|
|
484
|
+
);
|
|
485
|
+
process.on(
|
|
486
|
+
"beforeExit",
|
|
487
|
+
markInternal(() => {
|
|
488
|
+
void client2.flush(2e3);
|
|
489
|
+
})
|
|
490
|
+
);
|
|
458
491
|
return;
|
|
459
492
|
}
|
|
460
493
|
if (typeof window !== "undefined") {
|
|
@@ -750,25 +783,76 @@ var ManagerApi = class {
|
|
|
750
783
|
await sleep(delay);
|
|
751
784
|
}
|
|
752
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Um único PATCH com timeout curto — exit path (o processo está morrendo, o retry
|
|
788
|
+
* ladder do updateRun é proibido aqui). Status HTTP é ignorado: 409/404 significa
|
|
789
|
+
* que a run já foi cancelada/finalizada, o que é aceitável nesse caminho.
|
|
790
|
+
*/
|
|
791
|
+
async updateRunOnce(runId, update, timeoutMs = 2e3) {
|
|
792
|
+
await fetch(`${this.baseUrl}/runs/${encodeURIComponent(runId)}`, {
|
|
793
|
+
method: "PATCH",
|
|
794
|
+
headers: this.headers(),
|
|
795
|
+
body: JSON.stringify(update),
|
|
796
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
797
|
+
});
|
|
798
|
+
}
|
|
753
799
|
};
|
|
754
800
|
|
|
755
801
|
// src/manager/context.ts
|
|
802
|
+
function runCanceledError() {
|
|
803
|
+
return Object.assign(new Error("execu\xE7\xE3o cancelada"), { name: "RunCanceled" });
|
|
804
|
+
}
|
|
805
|
+
function cancelableSleep(ms, signal) {
|
|
806
|
+
return new Promise((resolve, reject) => {
|
|
807
|
+
if (signal.aborted) {
|
|
808
|
+
reject(runCanceledError());
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const onAbort = () => {
|
|
812
|
+
clearTimeout(timer);
|
|
813
|
+
reject(runCanceledError());
|
|
814
|
+
};
|
|
815
|
+
const timer = setTimeout(() => {
|
|
816
|
+
signal.removeEventListener("abort", onAbort);
|
|
817
|
+
resolve();
|
|
818
|
+
}, ms);
|
|
819
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
820
|
+
});
|
|
821
|
+
}
|
|
756
822
|
var DEFAULT_LOG_FLUSH_INTERVAL_MS = 2e3;
|
|
823
|
+
var DEFAULT_CANCEL_CHECK_INTERVAL_MS = 5e3;
|
|
824
|
+
var MIN_CANCEL_CHECK_INTERVAL_MS = 1e3;
|
|
825
|
+
var MAX_CANCEL_CHECK_INTERVAL_MS = 15e3;
|
|
757
826
|
var MAX_LOG_MESSAGE_CHARS = 1e4;
|
|
758
827
|
var MAX_LOGS_PER_RUN = 500;
|
|
759
828
|
var MAX_LOGS_PER_PATCH = 100;
|
|
760
829
|
var FLUSH_AT_BUFFERED = 20;
|
|
761
830
|
var RunReporter = class {
|
|
762
|
-
constructor(api, runId, task, logger, flushIntervalMs) {
|
|
831
|
+
constructor(api, runId, task, logger, flushIntervalMs, cancelCheckIntervalMs) {
|
|
763
832
|
this.api = api;
|
|
764
833
|
this.runId = runId;
|
|
765
834
|
this.logger = logger;
|
|
766
835
|
this.flushIntervalMs = flushIntervalMs ?? DEFAULT_LOG_FLUSH_INTERVAL_MS;
|
|
836
|
+
this.cancelCheckIntervalMs = Math.min(
|
|
837
|
+
MAX_CANCEL_CHECK_INTERVAL_MS,
|
|
838
|
+
Math.max(MIN_CANCEL_CHECK_INTERVAL_MS, cancelCheckIntervalMs ?? DEFAULT_CANCEL_CHECK_INTERVAL_MS)
|
|
839
|
+
);
|
|
840
|
+
const log = {
|
|
841
|
+
debug: (message) => this.log("debug", message),
|
|
842
|
+
info: (message) => this.log("info", message),
|
|
843
|
+
warn: (message) => this.log("warning", message),
|
|
844
|
+
error: (message) => this.log("error", message)
|
|
845
|
+
};
|
|
846
|
+
const signal = this.abortController.signal;
|
|
767
847
|
this.context = {
|
|
768
848
|
runId,
|
|
769
849
|
task,
|
|
770
|
-
log
|
|
771
|
-
signal
|
|
850
|
+
log,
|
|
851
|
+
signal,
|
|
852
|
+
throwIfCanceled: () => {
|
|
853
|
+
if (signal.aborted) throw runCanceledError();
|
|
854
|
+
},
|
|
855
|
+
sleep: (ms) => cancelableSleep(ms, signal)
|
|
772
856
|
};
|
|
773
857
|
}
|
|
774
858
|
context;
|
|
@@ -777,16 +861,31 @@ var RunReporter = class {
|
|
|
777
861
|
droppedCount = 0;
|
|
778
862
|
dead = false;
|
|
779
863
|
chain = Promise.resolve();
|
|
864
|
+
pendingUpdates = 0;
|
|
780
865
|
timer;
|
|
866
|
+
watchTimer;
|
|
781
867
|
abortController = new AbortController();
|
|
782
868
|
flushIntervalMs;
|
|
869
|
+
cancelCheckIntervalMs;
|
|
783
870
|
get isDead() {
|
|
784
871
|
return this.dead;
|
|
785
872
|
}
|
|
786
873
|
/** Aborta o ctx.signal sem matar o reporter (timeout/shutdown ainda reportam o terminal). */
|
|
787
874
|
abort() {
|
|
875
|
+
this.stopCancelWatch();
|
|
788
876
|
if (!this.abortController.signal.aborted) this.abortController.abort();
|
|
789
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Mata o reporter de vez: aborta o signal e suprime todo reporte futuro.
|
|
880
|
+
* Usado no exit path — depois que o agente reportou AgentTerminated por fora
|
|
881
|
+
* da fila, um handler zumbi não pode reenviar status por cima.
|
|
882
|
+
*/
|
|
883
|
+
kill() {
|
|
884
|
+
this.abort();
|
|
885
|
+
this.dead = true;
|
|
886
|
+
this.clearFlushTimer();
|
|
887
|
+
this.buffer = [];
|
|
888
|
+
}
|
|
790
889
|
log(level, message) {
|
|
791
890
|
if (this.dead) return;
|
|
792
891
|
if (this.loggedCount >= MAX_LOGS_PER_RUN) {
|
|
@@ -841,22 +940,45 @@ var RunReporter = class {
|
|
|
841
940
|
async reportRunning() {
|
|
842
941
|
this.enqueue({ status: "running" });
|
|
843
942
|
await this.chain;
|
|
943
|
+
if (!this.dead) this.startCancelWatch();
|
|
844
944
|
return !this.dead;
|
|
845
945
|
}
|
|
846
946
|
reportSuccess() {
|
|
947
|
+
this.stopCancelWatch();
|
|
847
948
|
this.clearFlushTimer();
|
|
848
949
|
this.enqueue({ status: "succeeded", logs: this.takeLogs() });
|
|
849
950
|
}
|
|
850
951
|
reportFailure(error) {
|
|
952
|
+
this.stopCancelWatch();
|
|
851
953
|
this.clearFlushTimer();
|
|
852
954
|
this.enqueue({ status: "failed", error, logs: this.takeLogs() });
|
|
853
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Heartbeat de cancelamento: enquanto o handler roda, um PATCH 'running' vazio
|
|
958
|
+
* periódico faz o servidor responder 409 se a run foi cancelada no app — o que
|
|
959
|
+
* mata o reporter e dispara ctx.signal, mesmo em handlers que nunca logam.
|
|
960
|
+
*/
|
|
961
|
+
startCancelWatch() {
|
|
962
|
+
if (this.watchTimer) return;
|
|
963
|
+
this.watchTimer = setInterval(() => {
|
|
964
|
+
if (this.dead || this.pendingUpdates > 0 || this.buffer.length > 0) return;
|
|
965
|
+
this.enqueue({ status: "running" });
|
|
966
|
+
}, this.cancelCheckIntervalMs);
|
|
967
|
+
unrefTimer(this.watchTimer);
|
|
968
|
+
}
|
|
969
|
+
stopCancelWatch() {
|
|
970
|
+
if (this.watchTimer) {
|
|
971
|
+
clearInterval(this.watchTimer);
|
|
972
|
+
this.watchTimer = void 0;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
854
975
|
/** Aguarda a fila de PATCHes esvaziar (chame depois do terminal). */
|
|
855
976
|
finalize() {
|
|
856
977
|
return this.chain;
|
|
857
978
|
}
|
|
858
979
|
enqueue(update) {
|
|
859
980
|
if (this.dead) return;
|
|
981
|
+
this.pendingUpdates++;
|
|
860
982
|
this.chain = this.chain.then(async () => {
|
|
861
983
|
if (this.dead) return;
|
|
862
984
|
const payload = update.logs && update.logs.length === 0 ? { ...update, logs: void 0 } : update;
|
|
@@ -865,6 +987,7 @@ var RunReporter = class {
|
|
|
865
987
|
} catch (error) {
|
|
866
988
|
if (error instanceof ManagerApiError && (error.status === 409 || error.status === 404)) {
|
|
867
989
|
this.dead = true;
|
|
990
|
+
this.stopCancelWatch();
|
|
868
991
|
this.abort();
|
|
869
992
|
this.buffer = [];
|
|
870
993
|
this.logger.warn(`run ${this.runId} cancelada/removida no servidor \u2014 reporte suprimido`);
|
|
@@ -872,10 +995,56 @@ var RunReporter = class {
|
|
|
872
995
|
}
|
|
873
996
|
this.logger.error(`update da run ${this.runId} perdido:`, error);
|
|
874
997
|
}
|
|
998
|
+
}).finally(() => {
|
|
999
|
+
this.pendingUpdates--;
|
|
875
1000
|
});
|
|
876
1001
|
}
|
|
877
1002
|
};
|
|
878
1003
|
|
|
1004
|
+
// src/manager/shutdown.ts
|
|
1005
|
+
var WATCHED_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
1006
|
+
function installShutdownHandlers(agent2) {
|
|
1007
|
+
if (!isNode()) return () => {
|
|
1008
|
+
};
|
|
1009
|
+
let signaled = false;
|
|
1010
|
+
const signalHandlers = /* @__PURE__ */ new Map();
|
|
1011
|
+
for (const signal of WATCHED_SIGNALS) {
|
|
1012
|
+
const handler = markInternal(() => {
|
|
1013
|
+
if (externalListenerCount(signal) > 0) return;
|
|
1014
|
+
if (signaled) return;
|
|
1015
|
+
signaled = true;
|
|
1016
|
+
const reraise = () => {
|
|
1017
|
+
process.removeListener(signal, handler);
|
|
1018
|
+
process.kill(process.pid, signal);
|
|
1019
|
+
};
|
|
1020
|
+
if (agent2.activeCount === 0) {
|
|
1021
|
+
reraise();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const report = agent2.reportAgentTerminated(`o processo do agente recebeu ${signal} durante a execu\xE7\xE3o`);
|
|
1025
|
+
addEmergencyTask(report);
|
|
1026
|
+
void report.finally(reraise);
|
|
1027
|
+
});
|
|
1028
|
+
process.on(signal, handler);
|
|
1029
|
+
signalHandlers.set(signal, handler);
|
|
1030
|
+
}
|
|
1031
|
+
const onFatal = markInternal((error) => {
|
|
1032
|
+
const report = agent2.activeCount > 0 ? agent2.reportAgentTerminated("o processo do agente morreu com um erro n\xE3o tratado durante a execu\xE7\xE3o") : Promise.resolve();
|
|
1033
|
+
addEmergencyTask(report);
|
|
1034
|
+
if (!hasInstalledGlobalHandlers() && externalListenerCount("uncaughtException") === 0) {
|
|
1035
|
+
void report.finally(() => {
|
|
1036
|
+
console.error(error);
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
process.on("uncaughtException", onFatal);
|
|
1042
|
+
return () => {
|
|
1043
|
+
for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
|
|
1044
|
+
process.removeListener("uncaughtException", onFatal);
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
879
1048
|
// src/manager/agent.ts
|
|
880
1049
|
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
881
1050
|
var MIN_POLL_INTERVAL_MS = 1e3;
|
|
@@ -898,6 +1067,7 @@ var ManagerAgent = class {
|
|
|
898
1067
|
this.instanceId = options.instanceId;
|
|
899
1068
|
this.taskTimeoutMs = options.taskTimeoutMs;
|
|
900
1069
|
this.logFlushIntervalMs = options.logFlushIntervalMs;
|
|
1070
|
+
this.cancelCheckIntervalMs = options.cancelCheckIntervalMs;
|
|
901
1071
|
this.logger.debug("agente configurado", { baseUrl: config.managerBaseUrl });
|
|
902
1072
|
}
|
|
903
1073
|
logger;
|
|
@@ -907,6 +1077,7 @@ var ManagerAgent = class {
|
|
|
907
1077
|
instanceId;
|
|
908
1078
|
taskTimeoutMs;
|
|
909
1079
|
logFlushIntervalMs;
|
|
1080
|
+
cancelCheckIntervalMs;
|
|
910
1081
|
state = "idle";
|
|
911
1082
|
timer;
|
|
912
1083
|
pollAbort;
|
|
@@ -914,6 +1085,7 @@ var ManagerAgent = class {
|
|
|
914
1085
|
consecutiveAuthFailures = 0;
|
|
915
1086
|
activeRuns = /* @__PURE__ */ new Set();
|
|
916
1087
|
reporters = /* @__PURE__ */ new Set();
|
|
1088
|
+
uninstallShutdown;
|
|
917
1089
|
get activeCount() {
|
|
918
1090
|
return this.activeRuns.size;
|
|
919
1091
|
}
|
|
@@ -926,6 +1098,9 @@ var ManagerAgent = class {
|
|
|
926
1098
|
throw new Error("[hobots-manager] nenhuma task registrada \u2014 chame register() antes de start()");
|
|
927
1099
|
}
|
|
928
1100
|
this.state = "started";
|
|
1101
|
+
if (this.options.installExitHandlers !== false && !this.uninstallShutdown) {
|
|
1102
|
+
this.uninstallShutdown = installShutdownHandlers(this);
|
|
1103
|
+
}
|
|
929
1104
|
this.logger.debug(`polling iniciado (tasks: ${[...this.registry.keys()].join(", ")})`);
|
|
930
1105
|
this.schedule(0);
|
|
931
1106
|
}
|
|
@@ -942,11 +1117,32 @@ var ManagerAgent = class {
|
|
|
942
1117
|
const finished = timeoutMs > 0 ? await Promise.race([all, sleep(timeoutMs).then(() => false)]) : false;
|
|
943
1118
|
if (!finished && this.activeRuns.size > 0) {
|
|
944
1119
|
this.logger.warn(`${this.activeRuns.size} run(s) ainda em execu\xE7\xE3o ap\xF3s ${timeoutMs}ms \u2014 abandonando a espera`);
|
|
945
|
-
|
|
1120
|
+
await this.reportAgentTerminated("agente encerrado durante a execu\xE7\xE3o (shutdown excedeu o tempo de espera)");
|
|
946
1121
|
}
|
|
1122
|
+
this.uninstallShutdown?.();
|
|
1123
|
+
this.uninstallShutdown = void 0;
|
|
947
1124
|
this.state = "idle";
|
|
948
1125
|
return finished;
|
|
949
1126
|
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Best-effort do exit path: reporta 'failed'/AgentTerminated para as runs ativas
|
|
1129
|
+
* antes do processo morrer, fora da fila do reporter e com timeout curto. Runs já
|
|
1130
|
+
* canceladas no app (reporter morto) são puladas; erros de rede são engolidos —
|
|
1131
|
+
* o sweep server-side é o backstop.
|
|
1132
|
+
*/
|
|
1133
|
+
async reportAgentTerminated(message, timeoutMs = 2500) {
|
|
1134
|
+
const pending = [];
|
|
1135
|
+
for (const reporter of this.reporters) {
|
|
1136
|
+
if (reporter.isDead) continue;
|
|
1137
|
+
reporter.kill();
|
|
1138
|
+
pending.push(
|
|
1139
|
+
this.api.updateRunOnce(reporter.context.runId, { status: "failed", error: { type: "AgentTerminated", message } }, timeoutMs).catch(() => {
|
|
1140
|
+
})
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (pending.length === 0) return;
|
|
1144
|
+
await Promise.race([Promise.allSettled(pending), sleep(timeoutMs)]);
|
|
1145
|
+
}
|
|
950
1146
|
/** Timer intencionalmente NÃO unref'd: o agente é a razão do processo existir. */
|
|
951
1147
|
schedule(delayMs) {
|
|
952
1148
|
if (this.state !== "started" || this.timer) return;
|
|
@@ -1013,7 +1209,7 @@ var ManagerAgent = class {
|
|
|
1013
1209
|
this.activeRuns.add(promise);
|
|
1014
1210
|
}
|
|
1015
1211
|
async executeRun(run) {
|
|
1016
|
-
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, this.logFlushIntervalMs);
|
|
1212
|
+
const reporter = new RunReporter(this.api, run.id, run.task, this.logger, this.logFlushIntervalMs, this.cancelCheckIntervalMs);
|
|
1017
1213
|
this.reporters.add(reporter);
|
|
1018
1214
|
try {
|
|
1019
1215
|
const handler = this.registry.get(run.task);
|