@kici-dev/agent 0.1.14 → 0.1.15
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 +13 -1
- package/dist/execution/cache/cache-engine.d.ts +62 -0
- package/dist/execution/cache/cache-phase.d.ts +29 -0
- package/dist/execution/cache/index.d.ts +9 -0
- package/dist/execution/dynamic-job-serializer.d.ts +12 -1
- package/dist/execution/env-init/init-phase.d.ts +64 -0
- package/dist/execution/init-runner.d.ts +1 -1
- package/dist/execution/job-runner.d.ts +21 -0
- package/dist/execution/sandbox/env-delta.d.ts +44 -0
- package/dist/execution/sandbox/env-file.d.ts +39 -0
- package/dist/execution/sandbox/index.d.ts +1 -1
- package/dist/execution/sandbox/ipc-protocol.d.ts +68 -3
- package/dist/execution/sandbox/job-deadline.d.ts +13 -0
- package/dist/execution/sandbox/step-loop.d.ts +29 -0
- package/dist/execution/sandbox/types.d.ts +11 -1
- package/dist/execution/sandbox/workflow-runner.d.ts +9 -1
- package/dist/execution/tmp-gc.d.ts +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +342 -12
- package/dist/server.js +399 -149
- package/dist/workflow-runner.js +1874 -950
- package/dist/ws/orchestrator-client.d.ts +18 -0
- package/package.json +14 -10
- package/sbom.spdx.json +60 -60
package/dist/server.js
CHANGED
|
@@ -8,16 +8,17 @@ import { PassThrough, Readable, Transform, Writable } from "node:stream";
|
|
|
8
8
|
import { serve } from "@hono/node-server";
|
|
9
9
|
import { Hono } from "hono";
|
|
10
10
|
import winston from "winston";
|
|
11
|
-
import { RingBuffer, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, deriveSharedSecret, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, normalizeLineEndings, requestContext, setServiceName, setupGracefulShutdown, sha256, sha256File, toErrorMessage, validateRequiredTools } from "@kici-dev/shared";
|
|
11
|
+
import { RingBuffer, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, deriveSharedSecret, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, logger, normalizeLineEndings, requestContext, setServiceName, setupGracefulShutdown, sha256, sha256File, toErrorMessage, validateRequiredTools } from "@kici-dev/shared";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
|
|
14
|
-
import { ALLOWED_SYSTEM_VARS, ExecutionJobStatus, ExecutionStepStatus, KNOWN_ROLES, PROTOCOL_VERSION, SANDBOX_DEFAULT_VARS, WS_CLOSE_AGENT_AUTH_FAILED, WS_MAX_PAYLOAD_BYTES, deriveOsArchLabels, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, resolveRoleLabels, validateNoReservedLabels } from "@kici-dev/engine";
|
|
14
|
+
import { ALLOWED_SYSTEM_VARS, CacheRunEventType, CacheStepType, ExecutionJobStatus, ExecutionStepStatus, InitFailureCategory, KNOWN_ROLES, PROTOCOL_VERSION, SANDBOX_DEFAULT_VARS, WS_CLOSE_AGENT_AUTH_FAILED, WS_MAX_PAYLOAD_BYTES, deriveOsArchLabels, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, resolveRoleLabels, validateNoReservedLabels } from "@kici-dev/engine";
|
|
15
15
|
import { execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
|
|
16
16
|
import WebSocket from "ws";
|
|
17
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
18
|
import { format, promisify } from "node:util";
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
20
|
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
21
|
+
import { gcStaleTmpDirs } from "@kici-dev/core/tmp-gc";
|
|
21
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
23
|
import fs, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
23
24
|
import Docker from "dockerode";
|
|
@@ -207,7 +208,7 @@ var LogBuffer = class extends RingBuffer {
|
|
|
207
208
|
};
|
|
208
209
|
//#endregion
|
|
209
210
|
//#region src/ws/orchestrator-client.ts
|
|
210
|
-
const logger$
|
|
211
|
+
const logger$11 = createLogger({ prefix: "orchestrator-client" });
|
|
211
212
|
/**
|
|
212
213
|
* WebSocket client that connects the agent to the customer orchestrator.
|
|
213
214
|
*
|
|
@@ -240,6 +241,8 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
240
241
|
pendingEventEmitRequests = /* @__PURE__ */ new Map();
|
|
241
242
|
/** Pending agent.api.request calls awaiting orchestrator response. */
|
|
242
243
|
pendingApiRequests = /* @__PURE__ */ new Map();
|
|
244
|
+
/** Pending user-cache restore/save requests awaiting orchestrator response. */
|
|
245
|
+
pendingUserCacheRequests = /* @__PURE__ */ new Map();
|
|
243
246
|
/** Pending concurrency report requests awaiting orchestrator ack. */
|
|
244
247
|
pendingConcurrencyRequests = /* @__PURE__ */ new Map();
|
|
245
248
|
url;
|
|
@@ -311,7 +314,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
311
314
|
*/
|
|
312
315
|
connect() {
|
|
313
316
|
if (this._state !== "disconnected") {
|
|
314
|
-
logger$
|
|
317
|
+
logger$11.warn("connect() called while not disconnected", { state: this._state });
|
|
315
318
|
return;
|
|
316
319
|
}
|
|
317
320
|
this.intentionalDisconnect = false;
|
|
@@ -481,6 +484,69 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
481
484
|
});
|
|
482
485
|
}
|
|
483
486
|
/**
|
|
487
|
+
* Relay a user-facing cache request from the sandbox to the orchestrator.
|
|
488
|
+
*
|
|
489
|
+
* Translates the sandbox `cache.request` IPC into the matching `cache.user.*`
|
|
490
|
+
* WS message and resolves with the orchestrator's response mapped onto the
|
|
491
|
+
* IPC response shape:
|
|
492
|
+
*
|
|
493
|
+
* - `restore` -> `cache.user.restore.request`, awaits `cache.user.restore.response`.
|
|
494
|
+
* - `beginSave` -> `cache.user.save.request`, awaits `cache.user.save.response`.
|
|
495
|
+
* - `completeSave` -> `cache.user.save.complete` (fire-and-forget; the
|
|
496
|
+
* orchestrator commits temp -> final without replying), resolves immediately.
|
|
497
|
+
*
|
|
498
|
+
* Times out after 30 seconds for the round-trip ops.
|
|
499
|
+
*/
|
|
500
|
+
async requestUserCache(jobId, request) {
|
|
501
|
+
if (request.op === "completeSave") {
|
|
502
|
+
this.sendDirect({
|
|
503
|
+
type: "cache.user.save.complete",
|
|
504
|
+
messageId: randomUUID(),
|
|
505
|
+
jobId,
|
|
506
|
+
key: request.key,
|
|
507
|
+
tarHash: request.tarHash,
|
|
508
|
+
sizeBytes: request.sizeBytes
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
type: "cache.response",
|
|
512
|
+
requestId: request.requestId
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const messageId = randomUUID();
|
|
516
|
+
return new Promise((resolve, reject) => {
|
|
517
|
+
const timer = setTimeout(() => {
|
|
518
|
+
this.pendingUserCacheRequests.delete(messageId);
|
|
519
|
+
reject(/* @__PURE__ */ new Error("User-cache request timed out (30s)"));
|
|
520
|
+
}, 3e4);
|
|
521
|
+
this.pendingUserCacheRequests.set(messageId, {
|
|
522
|
+
resolve: (response) => {
|
|
523
|
+
clearTimeout(timer);
|
|
524
|
+
resolve({
|
|
525
|
+
...response,
|
|
526
|
+
requestId: request.requestId
|
|
527
|
+
});
|
|
528
|
+
},
|
|
529
|
+
reject: (err) => {
|
|
530
|
+
clearTimeout(timer);
|
|
531
|
+
reject(err);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
if (request.op === "restore") this.sendDirect({
|
|
535
|
+
type: "cache.user.restore.request",
|
|
536
|
+
messageId,
|
|
537
|
+
jobId,
|
|
538
|
+
key: request.key,
|
|
539
|
+
...request.restoreKeys && { restoreKeys: request.restoreKeys }
|
|
540
|
+
});
|
|
541
|
+
else this.sendDirect({
|
|
542
|
+
type: "cache.user.save.request",
|
|
543
|
+
messageId,
|
|
544
|
+
jobId,
|
|
545
|
+
key: request.key
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
484
550
|
* Send a job.context message to the orchestrator.
|
|
485
551
|
*
|
|
486
552
|
* Conveys execution environment details (runtime, sandbox type, env vars)
|
|
@@ -562,7 +628,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
562
628
|
}
|
|
563
629
|
});
|
|
564
630
|
} catch (err) {
|
|
565
|
-
logger$
|
|
631
|
+
logger$11.error("Failed to create WebSocket", { error: toErrorMessage(err) });
|
|
566
632
|
this._state = "disconnected";
|
|
567
633
|
this.scheduleReconnect();
|
|
568
634
|
return;
|
|
@@ -570,7 +636,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
570
636
|
this.ws.on("open", () => {
|
|
571
637
|
if (this.token) {
|
|
572
638
|
this._state = "authenticating";
|
|
573
|
-
logger$
|
|
639
|
+
logger$11.info("Connected to orchestrator, sending auth.request", {
|
|
574
640
|
url: this.url,
|
|
575
641
|
agentId: this.agentId
|
|
576
642
|
});
|
|
@@ -581,7 +647,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
581
647
|
}));
|
|
582
648
|
} else {
|
|
583
649
|
this._state = "registering";
|
|
584
|
-
logger$
|
|
650
|
+
logger$11.info("Connected to orchestrator, sending agent.register (no token)", {
|
|
585
651
|
url: this.url,
|
|
586
652
|
agentId: this.agentId
|
|
587
653
|
});
|
|
@@ -592,12 +658,12 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
592
658
|
this.handleMessage(data);
|
|
593
659
|
});
|
|
594
660
|
this.ws.on("close", (code, reason) => {
|
|
595
|
-
logger$
|
|
661
|
+
logger$11.info("Orchestrator connection closed", {
|
|
596
662
|
code,
|
|
597
663
|
reason: reason.toString()
|
|
598
664
|
});
|
|
599
665
|
if (code === WS_CLOSE_AGENT_AUTH_FAILED) {
|
|
600
|
-
logger$
|
|
666
|
+
logger$11.error("Orchestrator closed with auth-failed code -- token is invalid or revoked. NOT retrying.", {
|
|
601
667
|
code,
|
|
602
668
|
reason: reason.toString()
|
|
603
669
|
});
|
|
@@ -614,12 +680,14 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
614
680
|
this.pendingEventEmitRequests.clear();
|
|
615
681
|
for (const [_id, pending] of this.pendingApiRequests) pending.reject(/* @__PURE__ */ new Error("WebSocket disconnected"));
|
|
616
682
|
this.pendingApiRequests.clear();
|
|
683
|
+
for (const [_id, pending] of this.pendingUserCacheRequests) pending.reject(/* @__PURE__ */ new Error("WebSocket disconnected"));
|
|
684
|
+
this.pendingUserCacheRequests.clear();
|
|
617
685
|
for (const [_id, pending] of this.pendingConcurrencyRequests) pending.reject(/* @__PURE__ */ new Error("WebSocket disconnected"));
|
|
618
686
|
this.pendingConcurrencyRequests.clear();
|
|
619
687
|
if (!this.intentionalDisconnect) this.scheduleReconnect();
|
|
620
688
|
});
|
|
621
689
|
this.ws.on("error", (err) => {
|
|
622
|
-
logger$
|
|
690
|
+
logger$11.error(`Orchestrator WebSocket error: ${err.message}`);
|
|
623
691
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.close();
|
|
624
692
|
});
|
|
625
693
|
}
|
|
@@ -628,7 +696,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
628
696
|
try {
|
|
629
697
|
raw = JSON.parse(data.toString());
|
|
630
698
|
} catch {
|
|
631
|
-
logger$
|
|
699
|
+
logger$11.warn("Malformed JSON received from orchestrator");
|
|
632
700
|
return;
|
|
633
701
|
}
|
|
634
702
|
const rawMsg = raw;
|
|
@@ -662,19 +730,37 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
662
730
|
}
|
|
663
731
|
return;
|
|
664
732
|
}
|
|
733
|
+
if (rawMsg.type === "cache.user.restore.response" || rawMsg.type === "cache.user.save.response") {
|
|
734
|
+
const cacheMsg = raw;
|
|
735
|
+
const pending = this.pendingUserCacheRequests.get(cacheMsg.requestId);
|
|
736
|
+
if (pending) {
|
|
737
|
+
this.pendingUserCacheRequests.delete(cacheMsg.requestId);
|
|
738
|
+
pending.resolve({
|
|
739
|
+
type: "cache.response",
|
|
740
|
+
requestId: cacheMsg.requestId,
|
|
741
|
+
...cacheMsg.hit !== void 0 && { hit: cacheMsg.hit },
|
|
742
|
+
...cacheMsg.matchedKey && { matchedKey: cacheMsg.matchedKey },
|
|
743
|
+
...cacheMsg.downloadUrl && { downloadUrl: cacheMsg.downloadUrl },
|
|
744
|
+
...cacheMsg.tarHash && { tarHash: cacheMsg.tarHash },
|
|
745
|
+
...cacheMsg.skip !== void 0 && { skip: cacheMsg.skip },
|
|
746
|
+
...cacheMsg.uploadUrl && { uploadUrl: cacheMsg.uploadUrl }
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
665
751
|
const parsed = orchestratorToAgentMessageSchema.safeParse(raw);
|
|
666
752
|
if (parsed.success) {
|
|
667
753
|
const msg = parsed.data;
|
|
668
754
|
switch (msg.type) {
|
|
669
755
|
case "auth.success":
|
|
670
756
|
if (this._state === "authenticating") {
|
|
671
|
-
logger$
|
|
757
|
+
logger$11.info("Authentication successful, sending agent.register", { connectionId: msg.connectionId });
|
|
672
758
|
this._state = "registering";
|
|
673
759
|
this.sendAgentRegister();
|
|
674
760
|
}
|
|
675
761
|
break;
|
|
676
762
|
case "auth.failure":
|
|
677
|
-
logger$
|
|
763
|
+
logger$11.error("Authentication FAILED -- token is invalid or expired. NOT retrying.", { reason: msg.reason });
|
|
678
764
|
this.authFailed = true;
|
|
679
765
|
this.intentionalDisconnect = true;
|
|
680
766
|
if (this.ws) {
|
|
@@ -684,7 +770,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
684
770
|
this._state = "disconnected";
|
|
685
771
|
break;
|
|
686
772
|
case "register.ack":
|
|
687
|
-
logger$
|
|
773
|
+
logger$11.info("Registration acknowledged by orchestrator", {
|
|
688
774
|
agentId: msg.agentId,
|
|
689
775
|
labels: msg.labels,
|
|
690
776
|
scalerManaged: msg.scalerManaged,
|
|
@@ -699,14 +785,14 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
699
785
|
this.sendConfigAck(msg.agentId);
|
|
700
786
|
break;
|
|
701
787
|
case "job.dispatch":
|
|
702
|
-
logger$
|
|
788
|
+
logger$11.info("Job dispatch received", {
|
|
703
789
|
runId: msg.runId,
|
|
704
790
|
jobId: msg.jobId
|
|
705
791
|
});
|
|
706
792
|
this.onJobDispatch(msg);
|
|
707
793
|
break;
|
|
708
794
|
case "job.cancel":
|
|
709
|
-
logger$
|
|
795
|
+
logger$11.info("Job cancel received", {
|
|
710
796
|
runId: msg.runId,
|
|
711
797
|
jobId: msg.jobId,
|
|
712
798
|
reason: msg.reason
|
|
@@ -714,7 +800,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
714
800
|
this.onJobCancel(msg);
|
|
715
801
|
break;
|
|
716
802
|
case "job.concurrency.ack": {
|
|
717
|
-
logger$
|
|
803
|
+
logger$11.info("Concurrency ack received", {
|
|
718
804
|
requestId: msg.requestId,
|
|
719
805
|
action: msg.action
|
|
720
806
|
});
|
|
@@ -732,7 +818,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
732
818
|
return;
|
|
733
819
|
}
|
|
734
820
|
if (heartbeatSchema.safeParse(raw).success) return;
|
|
735
|
-
logger$
|
|
821
|
+
logger$11.warn("Invalid message from orchestrator", { errors: parsed.error.issues });
|
|
736
822
|
}
|
|
737
823
|
flushBuffer() {
|
|
738
824
|
const events = this.eventBuffer.flush();
|
|
@@ -746,11 +832,11 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
746
832
|
}
|
|
747
833
|
this.disconnectedAt = null;
|
|
748
834
|
if (events.length > 0) {
|
|
749
|
-
logger$
|
|
835
|
+
logger$11.info("Flushing event buffer", { count: events.length });
|
|
750
836
|
for (const msg of events) this.sendDirect(msg);
|
|
751
837
|
}
|
|
752
838
|
if (logLines.length > 0) {
|
|
753
|
-
logger$
|
|
839
|
+
logger$11.info("Flushing log buffer", { count: logLines.length });
|
|
754
840
|
for (let i = 0; i < logLines.length; i += OrchestratorClient.LOG_BATCH_SIZE) {
|
|
755
841
|
const batch = logLines.slice(i, i + OrchestratorClient.LOG_BATCH_SIZE);
|
|
756
842
|
this.sendAgentLogMessage(batch);
|
|
@@ -803,14 +889,14 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
803
889
|
*/
|
|
804
890
|
blockMmdsAccess() {
|
|
805
891
|
if (process.getuid?.() !== 0) {
|
|
806
|
-
logger$
|
|
892
|
+
logger$11.info("MMDS iptables block skipped (non-root) — network isolation handled by orchestrator");
|
|
807
893
|
return;
|
|
808
894
|
}
|
|
809
895
|
try {
|
|
810
896
|
execSync("iptables -A OUTPUT -d 169.254.169.254 -j DROP", { timeout: 5e3 });
|
|
811
|
-
logger$
|
|
897
|
+
logger$11.info("MMDS access blocked via iptables");
|
|
812
898
|
} catch (err) {
|
|
813
|
-
logger$
|
|
899
|
+
logger$11.warn("Failed to block MMDS access via iptables", { error: toErrorMessage(err) });
|
|
814
900
|
}
|
|
815
901
|
}
|
|
816
902
|
/**
|
|
@@ -824,7 +910,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
824
910
|
messageId: `config-ack-${agentId}-${Date.now()}`,
|
|
825
911
|
agentId
|
|
826
912
|
}));
|
|
827
|
-
logger$
|
|
913
|
+
logger$11.info("Config ACK sent to orchestrator", { agentId });
|
|
828
914
|
}
|
|
829
915
|
}
|
|
830
916
|
startHeartbeat() {
|
|
@@ -885,12 +971,12 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
885
971
|
scheduleReconnect() {
|
|
886
972
|
this.cancelReconnect();
|
|
887
973
|
if (this.authFailed) {
|
|
888
|
-
logger$
|
|
974
|
+
logger$11.error("Not reconnecting: authentication permanently failed");
|
|
889
975
|
return;
|
|
890
976
|
}
|
|
891
977
|
const delay = this.getReconnectDelay();
|
|
892
978
|
this.reconnectAttempts++;
|
|
893
|
-
logger$
|
|
979
|
+
logger$11.info("Scheduling reconnect", {
|
|
894
980
|
attempt: this.reconnectAttempts,
|
|
895
981
|
delayMs: Math.round(delay)
|
|
896
982
|
});
|
|
@@ -975,14 +1061,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
975
1061
|
init_console_capture();
|
|
976
1062
|
function safe(name, fallback = "unknown") {
|
|
977
1063
|
switch (name) {
|
|
978
|
-
case "version": return "0.1.
|
|
979
|
-
case "buildCommit": return "
|
|
980
|
-
case "sdkVersion": return "0.1.
|
|
981
|
-
case "sdkBundleHash": return "
|
|
982
|
-
case "sharedVersion": return "0.1.
|
|
983
|
-
case "sharedBundleHash": return "
|
|
984
|
-
case "engineVersion": return "0.1.
|
|
985
|
-
case "engineBundleHash": return "
|
|
1064
|
+
case "version": return "0.1.15";
|
|
1065
|
+
case "buildCommit": return "831f6a763";
|
|
1066
|
+
case "sdkVersion": return "0.1.15";
|
|
1067
|
+
case "sdkBundleHash": return "cee24b67bbb4a1d2a770e4063dd37a9af33409faeb5ce32f936703b4431316dd";
|
|
1068
|
+
case "sharedVersion": return "0.1.15";
|
|
1069
|
+
case "sharedBundleHash": return "e82a8b68a7d72698674352158c5de4c0d10824c61bfa7166cc8f846044b8b5e4";
|
|
1070
|
+
case "engineVersion": return "0.1.15";
|
|
1071
|
+
case "engineBundleHash": return "032d6b28b3d32bcbea80ba80a52934b017ee2c86a97fcaf8fe8db0d86d72a7f7";
|
|
986
1072
|
default: return fallback;
|
|
987
1073
|
}
|
|
988
1074
|
}
|
|
@@ -1203,6 +1289,42 @@ function verifyNpmAvailable() {
|
|
|
1203
1289
|
}
|
|
1204
1290
|
var init_npm_resolver = __esmMin((() => {}));
|
|
1205
1291
|
//#endregion
|
|
1292
|
+
//#region src/execution/tmp-gc.ts
|
|
1293
|
+
init_npm_resolver();
|
|
1294
|
+
/**
|
|
1295
|
+
* Startup garbage collection for this agent's own temp-directory families.
|
|
1296
|
+
*
|
|
1297
|
+
* Job workdirs (`kici-<6 random chars>`, see job-runner.ts) and isolated
|
|
1298
|
+
* pnpm stores (`kici-pnpm-store-*`, see dep-installer.ts) clean themselves
|
|
1299
|
+
* up in `finally` blocks — but a hard process death (SIGKILL, OOM kill)
|
|
1300
|
+
* skips those, and on a long-lived bare-metal agent the leftovers then
|
|
1301
|
+
* accumulate forever. Collecting anything older than a day at startup is
|
|
1302
|
+
* safe on shared hosts: no job lives remotely that long (job timeouts are
|
|
1303
|
+
* minutes), so a concurrent agent's in-flight dirs are never eligible.
|
|
1304
|
+
*/
|
|
1305
|
+
const AGENT_TMP_GC_MAX_AGE_MS = 1440 * 60 * 1e3;
|
|
1306
|
+
/** mkdtemp's 6-char suffix on the bare `kici-` prefix — job workdirs only. */
|
|
1307
|
+
const AGENT_WORKDIR_PATTERN = /^kici-[A-Za-z0-9]{6}$/;
|
|
1308
|
+
const PNPM_STORE_PATTERN = /^kici-pnpm-store-/;
|
|
1309
|
+
/**
|
|
1310
|
+
* Collect this agent's stale temp dirs. `base` is overridable for tests;
|
|
1311
|
+
* production callers use the default temp root. Never throws.
|
|
1312
|
+
*/
|
|
1313
|
+
async function gcStaleAgentTmpDirs(base = tmpdir()) {
|
|
1314
|
+
const log = (m) => logger.info(m);
|
|
1315
|
+
return [...await gcStaleTmpDirs({
|
|
1316
|
+
base,
|
|
1317
|
+
pattern: AGENT_WORKDIR_PATTERN,
|
|
1318
|
+
maxAgeMs: AGENT_TMP_GC_MAX_AGE_MS,
|
|
1319
|
+
log
|
|
1320
|
+
}), ...await gcStaleTmpDirs({
|
|
1321
|
+
base,
|
|
1322
|
+
pattern: PNPM_STORE_PATTERN,
|
|
1323
|
+
maxAgeMs: AGENT_TMP_GC_MAX_AGE_MS,
|
|
1324
|
+
log
|
|
1325
|
+
})];
|
|
1326
|
+
}
|
|
1327
|
+
//#endregion
|
|
1206
1328
|
//#region src/metrics/prometheus.ts
|
|
1207
1329
|
var prometheus_exports = /* @__PURE__ */ __exportAll({
|
|
1208
1330
|
cloneDurationSeconds: () => cloneDurationSeconds,
|
|
@@ -1630,8 +1752,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
1630
1752
|
}
|
|
1631
1753
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
1632
1754
|
var init_workflow_loader = __esmMin((() => {
|
|
1633
|
-
AGENT_SDK_VERSION = "0.1.
|
|
1634
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
1755
|
+
AGENT_SDK_VERSION = "0.1.15";
|
|
1756
|
+
AGENT_SDK_BUNDLE_HASH = "cee24b67bbb4a1d2a770e4063dd37a9af33409faeb5ce32f936703b4431316dd";
|
|
1635
1757
|
hookRegistered = false;
|
|
1636
1758
|
}));
|
|
1637
1759
|
//#endregion
|
|
@@ -1652,7 +1774,7 @@ var init_workflow_loader = __esmMin((() => {
|
|
|
1652
1774
|
async function packKiciSource(workDir) {
|
|
1653
1775
|
const kiciDir = join(workDir, ".kici");
|
|
1654
1776
|
if (!existsSync(kiciDir)) throw new Error(`.kici/ not found at ${kiciDir}`);
|
|
1655
|
-
logger$
|
|
1777
|
+
logger$10.info("Packing .kici/ source tarball", { dir: workDir });
|
|
1656
1778
|
const startTime = Date.now();
|
|
1657
1779
|
const stream = c({
|
|
1658
1780
|
gzip: true,
|
|
@@ -1666,7 +1788,7 @@ async function packKiciSource(workDir) {
|
|
|
1666
1788
|
const hash = sha256(tarball);
|
|
1667
1789
|
const sizeKB = (tarball.length / 1024).toFixed(2);
|
|
1668
1790
|
const durationMs = Date.now() - startTime;
|
|
1669
|
-
logger$
|
|
1791
|
+
logger$10.info(".kici/ source packed", {
|
|
1670
1792
|
sizeKB,
|
|
1671
1793
|
hash: hash.slice(0, 12),
|
|
1672
1794
|
durationMs
|
|
@@ -1676,9 +1798,9 @@ async function packKiciSource(workDir) {
|
|
|
1676
1798
|
hash
|
|
1677
1799
|
};
|
|
1678
1800
|
}
|
|
1679
|
-
var logger$
|
|
1801
|
+
var logger$10;
|
|
1680
1802
|
var init_source_packer = __esmMin((() => {
|
|
1681
|
-
logger$
|
|
1803
|
+
logger$10 = createLogger({ prefix: "source-packer" });
|
|
1682
1804
|
}));
|
|
1683
1805
|
//#endregion
|
|
1684
1806
|
//#region src/execution/dep-restore.ts
|
|
@@ -1819,7 +1941,7 @@ async function cleanupScratch(scratchDir) {
|
|
|
1819
1941
|
force: true
|
|
1820
1942
|
});
|
|
1821
1943
|
} catch (cleanupErr) {
|
|
1822
|
-
logger$
|
|
1944
|
+
logger$9.warn("Scratch dir cleanup failed (orphan left behind)", {
|
|
1823
1945
|
scratchDir,
|
|
1824
1946
|
error: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)
|
|
1825
1947
|
});
|
|
@@ -1844,7 +1966,7 @@ async function cleanupScratch(scratchDir) {
|
|
|
1844
1966
|
*/
|
|
1845
1967
|
async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
1846
1968
|
depsUrl = resolveOrchestratorUrl(depsUrl);
|
|
1847
|
-
logger$
|
|
1969
|
+
logger$9.info("Downloading dependency tarball", { url: depsUrl });
|
|
1848
1970
|
const kiciDir = join(workDir, ".kici");
|
|
1849
1971
|
if (depsUrl.startsWith("file://")) {
|
|
1850
1972
|
const localPath = fileURLToPath(depsUrl);
|
|
@@ -1858,7 +1980,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
1858
1980
|
await moveScratchIntoRepo(scratchDir, workDir);
|
|
1859
1981
|
await cleanupScratch(scratchDir);
|
|
1860
1982
|
const sizeMB = (data.length / (1024 * 1024)).toFixed(2);
|
|
1861
|
-
logger$
|
|
1983
|
+
logger$9.info("Dependencies restored from cache (file)", {
|
|
1862
1984
|
sizeMB,
|
|
1863
1985
|
targetDir: workDir
|
|
1864
1986
|
});
|
|
@@ -1867,7 +1989,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
1867
1989
|
if (!depsUrl.startsWith("http://") && !depsUrl.startsWith("https://")) throw new Error(`Unsupported deps URL scheme: ${depsUrl}`);
|
|
1868
1990
|
let lastError;
|
|
1869
1991
|
for (let attempt = 0; attempt <= 2; attempt++) {
|
|
1870
|
-
if (attempt > 0) logger$
|
|
1992
|
+
if (attempt > 0) logger$9.warn("Retrying dep tarball download", {
|
|
1871
1993
|
attempt,
|
|
1872
1994
|
url: depsUrl
|
|
1873
1995
|
});
|
|
@@ -1876,11 +1998,11 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
1876
1998
|
if (depsHash && hash !== depsHash) throw new Error(`Dep tarball hash mismatch: expected ${depsHash}, got ${hash}`);
|
|
1877
1999
|
await moveScratchIntoRepo(scratchDir, workDir);
|
|
1878
2000
|
await cleanupScratch(scratchDir);
|
|
1879
|
-
logger$
|
|
2001
|
+
logger$9.info("Dependencies restored from cache (stream)", { targetDir: workDir });
|
|
1880
2002
|
return;
|
|
1881
2003
|
} catch (err) {
|
|
1882
2004
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1883
|
-
logger$
|
|
2005
|
+
logger$9.warn("Dep tarball download failed", {
|
|
1884
2006
|
attempt,
|
|
1885
2007
|
error: lastError.message
|
|
1886
2008
|
});
|
|
@@ -1888,9 +2010,9 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
1888
2010
|
}
|
|
1889
2011
|
throw new Error(`Dep tarball download failed after 3 attempts: ${lastError?.message}`);
|
|
1890
2012
|
}
|
|
1891
|
-
var logger$
|
|
2013
|
+
var logger$9, DOWNLOAD_TIMEOUT_MS$1, SCRATCH_DIR_BASENAME_PREFIX;
|
|
1892
2014
|
var init_dep_restore = __esmMin((() => {
|
|
1893
|
-
logger$
|
|
2015
|
+
logger$9 = createLogger({ prefix: "dep-restore" });
|
|
1894
2016
|
DOWNLOAD_TIMEOUT_MS$1 = 300 * 1e3;
|
|
1895
2017
|
SCRATCH_DIR_BASENAME_PREFIX = ".dep-restore-scratch-";
|
|
1896
2018
|
`${SCRATCH_DIR_BASENAME_PREFIX}`;
|
|
@@ -2002,7 +2124,7 @@ async function extractSourceTarball(data, targetDir) {
|
|
|
2002
2124
|
}
|
|
2003
2125
|
async function restoreSource(workDir, sourceTarUrl) {
|
|
2004
2126
|
sourceTarUrl = resolveOrchestratorUrl(sourceTarUrl);
|
|
2005
|
-
logger$
|
|
2127
|
+
logger$8.info("Restoring .kici/ source from tarball", { sourceTarUrl });
|
|
2006
2128
|
const startTime = Date.now();
|
|
2007
2129
|
let data;
|
|
2008
2130
|
if (sourceTarUrl.startsWith("file://")) {
|
|
@@ -2012,16 +2134,16 @@ async function restoreSource(workDir, sourceTarUrl) {
|
|
|
2012
2134
|
else throw new Error(`Unsupported source tarball URL scheme: ${sourceTarUrl}`);
|
|
2013
2135
|
await extractSourceTarball(data, workDir);
|
|
2014
2136
|
const durationMs = Date.now() - startTime;
|
|
2015
|
-
logger$
|
|
2137
|
+
logger$8.info(".kici/ source restored", {
|
|
2016
2138
|
sizeKB: (data.length / 1024).toFixed(2),
|
|
2017
2139
|
durationMs
|
|
2018
2140
|
});
|
|
2019
2141
|
}
|
|
2020
|
-
var logger$
|
|
2142
|
+
var logger$8;
|
|
2021
2143
|
var init_source_restore = __esmMin((() => {
|
|
2022
2144
|
init_download();
|
|
2023
2145
|
init_dep_restore();
|
|
2024
|
-
logger$
|
|
2146
|
+
logger$8 = createLogger({ prefix: "source-restore" });
|
|
2025
2147
|
}));
|
|
2026
2148
|
//#endregion
|
|
2027
2149
|
//#region src/execution/timeout-util.ts
|
|
@@ -2069,7 +2191,7 @@ function findJobByName(workflow, jobName) {
|
|
|
2069
2191
|
*
|
|
2070
2192
|
* @param workflow - The extracted Workflow object
|
|
2071
2193
|
* @param jobName - Name of the job whose dynamic fields to evaluate
|
|
2072
|
-
* @param event - Normalized
|
|
2194
|
+
* @param event - Normalized event envelope — same shape every dynamic-function call site receives.
|
|
2073
2195
|
* @param flags - Which fields are dynamic and need evaluation
|
|
2074
2196
|
* @param timeoutMs - Timeout per dynamic function call (default 60_000ms)
|
|
2075
2197
|
*/
|
|
@@ -2258,7 +2380,12 @@ async function serializeMatrix(matrix, jobName, runsOn, ctx) {
|
|
|
2258
2380
|
log: ctx.log,
|
|
2259
2381
|
env: ctx.env
|
|
2260
2382
|
};
|
|
2261
|
-
|
|
2383
|
+
let values;
|
|
2384
|
+
try {
|
|
2385
|
+
values = await withTimeout(() => matrix(matrixCtx), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic matrix for generated job '${jobName}'`);
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
throw new MatrixExpansionError(jobName, `Matrix expansion failed for job '${jobName}': ${err.message}`);
|
|
2388
|
+
}
|
|
2262
2389
|
if (Array.isArray(values)) return {
|
|
2263
2390
|
_type: "static",
|
|
2264
2391
|
values
|
|
@@ -2267,11 +2394,19 @@ async function serializeMatrix(matrix, jobName, runsOn, ctx) {
|
|
|
2267
2394
|
_type: "static",
|
|
2268
2395
|
values
|
|
2269
2396
|
};
|
|
2270
|
-
throw new
|
|
2397
|
+
throw new MatrixExpansionError(jobName, `Job '${jobName}': dynamic matrix function returned an unsupported value (expected array or object, got ${typeof values})`);
|
|
2271
2398
|
}
|
|
2272
|
-
var DYNAMIC_FIELD_TIMEOUT_MS;
|
|
2399
|
+
var MatrixExpansionError, DYNAMIC_FIELD_TIMEOUT_MS;
|
|
2273
2400
|
var init_dynamic_job_serializer = __esmMin((() => {
|
|
2274
2401
|
init_timeout_util();
|
|
2402
|
+
MatrixExpansionError = class MatrixExpansionError extends Error {
|
|
2403
|
+
name = "MatrixExpansionError";
|
|
2404
|
+
constructor(jobName, message) {
|
|
2405
|
+
super(message);
|
|
2406
|
+
this.jobName = jobName;
|
|
2407
|
+
Object.setPrototypeOf(this, MatrixExpansionError.prototype);
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2275
2410
|
DYNAMIC_FIELD_TIMEOUT_MS = 6e4;
|
|
2276
2411
|
})), DEFAULT_MAX_LOG_SIZE_BYTES, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_LINE_THRESHOLD, PAUSE_SAFETY_TIMEOUT_MS, LogStreamer;
|
|
2277
2412
|
var init_log_streamer = __esmMin((() => {
|
|
@@ -2540,7 +2675,7 @@ async function applyOverlay(config) {
|
|
|
2540
2675
|
const { tarballUrl, cliPublicKey, orchestratorPrivateKey, repoDir } = config;
|
|
2541
2676
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
|
|
2542
2677
|
try {
|
|
2543
|
-
logger$
|
|
2678
|
+
logger$7.info("Downloading overlay tarball", { url: tarballUrl.replace(/\?.*$/, "?[redacted]") });
|
|
2544
2679
|
let encryptedData;
|
|
2545
2680
|
try {
|
|
2546
2681
|
encryptedData = await downloadUrl(tarballUrl);
|
|
@@ -2550,7 +2685,7 @@ async function applyOverlay(config) {
|
|
|
2550
2685
|
const cliPubKeyBuf = Buffer.from(cliPublicKey, "base64");
|
|
2551
2686
|
const aesKey = deriveSharedSecret(Buffer.from(orchestratorPrivateKey, "base64"), cliPubKeyBuf);
|
|
2552
2687
|
const decryptedData = decryptBuffer(encryptedData, aesKey);
|
|
2553
|
-
logger$
|
|
2688
|
+
logger$7.info("Extracting overlay tarball", { size: decryptedData.length });
|
|
2554
2689
|
const extractDir = path.join(tmpDir, "extracted");
|
|
2555
2690
|
await fs.mkdir(extractDir, { recursive: true });
|
|
2556
2691
|
try {
|
|
@@ -2599,10 +2734,10 @@ async function applyOverlay(config) {
|
|
|
2599
2734
|
await fs.unlink(targetPath);
|
|
2600
2735
|
filesDeleted++;
|
|
2601
2736
|
} catch {
|
|
2602
|
-
logger$
|
|
2737
|
+
logger$7.debug("Deletion target not found, skipping", { file });
|
|
2603
2738
|
}
|
|
2604
2739
|
}
|
|
2605
|
-
logger$
|
|
2740
|
+
logger$7.info("Overlay applied successfully", {
|
|
2606
2741
|
filesApplied,
|
|
2607
2742
|
filesDeleted
|
|
2608
2743
|
});
|
|
@@ -2618,10 +2753,10 @@ async function applyOverlay(config) {
|
|
|
2618
2753
|
}).catch(() => {});
|
|
2619
2754
|
}
|
|
2620
2755
|
}
|
|
2621
|
-
var logger$
|
|
2756
|
+
var logger$7, IV_LENGTH$1, AUTH_TAG_LENGTH;
|
|
2622
2757
|
var init_overlay_applier = __esmMin((() => {
|
|
2623
2758
|
init_download();
|
|
2624
|
-
logger$
|
|
2759
|
+
logger$7 = createLogger({ prefix: "overlay-applier" });
|
|
2625
2760
|
IV_LENGTH$1 = 12;
|
|
2626
2761
|
AUTH_TAG_LENGTH = 16;
|
|
2627
2762
|
}));
|
|
@@ -2935,7 +3070,7 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
2935
3070
|
async function installDeps(kiciDir, opts = {}) {
|
|
2936
3071
|
const repoRoot = opts.repoRoot ?? dirname(kiciDir);
|
|
2937
3072
|
const packageManager = await detectKiciPackageManager(repoRoot, kiciDir);
|
|
2938
|
-
logger$
|
|
3073
|
+
logger$6.info("Installing deps inline", {
|
|
2939
3074
|
packageManager,
|
|
2940
3075
|
dir: kiciDir
|
|
2941
3076
|
});
|
|
@@ -2976,7 +3111,7 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
2976
3111
|
if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
|
|
2977
3112
|
const durationMs = Date.now() - startTime;
|
|
2978
3113
|
process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
|
|
2979
|
-
logger$
|
|
3114
|
+
logger$6.info("Deps installed inline", {
|
|
2980
3115
|
packageManager,
|
|
2981
3116
|
durationMs
|
|
2982
3117
|
});
|
|
@@ -3114,12 +3249,11 @@ function logSubprocessStreams(e, tokens) {
|
|
|
3114
3249
|
if (e && typeof e === "object" && "stdout" in e) process.stderr.write(`[dep-installer:trace] stdout: ${redactNpmOutput(String(e.stdout), tokens).slice(0, 500)}\n`);
|
|
3115
3250
|
if (e && typeof e === "object" && "stderr" in e) process.stderr.write(`[dep-installer:trace] stderr: ${redactNpmOutput(String(e.stderr), tokens).slice(0, 500)}\n`);
|
|
3116
3251
|
}
|
|
3117
|
-
var logger$
|
|
3252
|
+
var logger$6, execFileAsync, INSTALL_TIMEOUT_MS, INSTALL_MAX_BUFFER;
|
|
3118
3253
|
var init_dep_installer = __esmMin((() => {
|
|
3119
|
-
init_npm_resolver();
|
|
3120
3254
|
init_npm_registry_config();
|
|
3121
3255
|
init_validate_kici_deps();
|
|
3122
|
-
logger$
|
|
3256
|
+
logger$6 = createLogger({ prefix: "dep-installer" });
|
|
3123
3257
|
execFileAsync = promisify(execFile);
|
|
3124
3258
|
INSTALL_TIMEOUT_MS = 6e5;
|
|
3125
3259
|
INSTALL_MAX_BUFFER = 128 * 1024 * 1024;
|
|
@@ -3158,7 +3292,7 @@ async function packNodeModules(kiciDir) {
|
|
|
3158
3292
|
const workDir = dirname(kiciDir);
|
|
3159
3293
|
const packageManager = await detectPackageManagerFromManifests(workDir) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
3160
3294
|
const entries = await closureEntries(workDir, kiciDir, packageManager);
|
|
3161
|
-
logger$
|
|
3295
|
+
logger$5.info("Packing dependency closure into tarball", {
|
|
3162
3296
|
dir: workDir,
|
|
3163
3297
|
packageManager,
|
|
3164
3298
|
entries
|
|
@@ -3174,7 +3308,7 @@ async function packNodeModules(kiciDir) {
|
|
|
3174
3308
|
const tarball = Buffer.concat(chunks);
|
|
3175
3309
|
const hash = sha256(tarball);
|
|
3176
3310
|
const sizeMB = (tarball.length / (1024 * 1024)).toFixed(2);
|
|
3177
|
-
logger$
|
|
3311
|
+
logger$5.info("Dependency closure packed", {
|
|
3178
3312
|
sizeMB,
|
|
3179
3313
|
hash: hash.slice(0, 12),
|
|
3180
3314
|
durationMs: Date.now() - startTime
|
|
@@ -3262,9 +3396,9 @@ function isInside(root, target) {
|
|
|
3262
3396
|
function isAbsoluteRel(rel) {
|
|
3263
3397
|
return rel.length > 1 && rel[1] === ":";
|
|
3264
3398
|
}
|
|
3265
|
-
var logger$
|
|
3399
|
+
var logger$5;
|
|
3266
3400
|
var init_dep_packer = __esmMin((() => {
|
|
3267
|
-
logger$
|
|
3401
|
+
logger$5 = createLogger({ prefix: "dep-packer" });
|
|
3268
3402
|
}));
|
|
3269
3403
|
//#endregion
|
|
3270
3404
|
//#region src/execution/sandbox/env-sanitizer.ts
|
|
@@ -3427,6 +3561,7 @@ function buildRequest(dispatch, workDir) {
|
|
|
3427
3561
|
contentHash: jobConfig.contentHash,
|
|
3428
3562
|
resolvedHashFiles: jobConfig.resolvedHashFiles,
|
|
3429
3563
|
maxLogSizeBytes: dispatch.maxLogSizeBytes,
|
|
3564
|
+
jobTimeoutMs: jobConfig.timeout,
|
|
3430
3565
|
container: jobConfig.container,
|
|
3431
3566
|
event: jobConfig.event,
|
|
3432
3567
|
provider: jobConfig.provider,
|
|
@@ -3668,6 +3803,24 @@ function relayAgentApiRequest(msg, ctx) {
|
|
|
3668
3803
|
error: toErrorMessage(err)
|
|
3669
3804
|
}));
|
|
3670
3805
|
}
|
|
3806
|
+
/** Relay `cache.request` and pipe the orchestrator response (or an error
|
|
3807
|
+
* response, or a "not configured" response when the agent didn't provide
|
|
3808
|
+
* the callback) back into the sandbox runner. */
|
|
3809
|
+
function relayCacheRequest$1(msg, ctx) {
|
|
3810
|
+
if (!ctx.execOptions.onCacheRequest) {
|
|
3811
|
+
safeSendToChild(ctx.child, {
|
|
3812
|
+
type: "cache.response",
|
|
3813
|
+
requestId: msg.requestId,
|
|
3814
|
+
error: "Cache not available in this agent configuration"
|
|
3815
|
+
});
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
ctx.execOptions.onCacheRequest(msg).then((response) => safeSendToChild(ctx.child, response), (err) => safeSendToChild(ctx.child, {
|
|
3819
|
+
type: "cache.response",
|
|
3820
|
+
requestId: msg.requestId,
|
|
3821
|
+
error: toErrorMessage(err)
|
|
3822
|
+
}));
|
|
3823
|
+
}
|
|
3671
3824
|
/** Resolve the result promise for `job.complete` IPC messages. Encrypts
|
|
3672
3825
|
* secret outputs (when a runPublicKey is available) and overrides status to
|
|
3673
3826
|
* `cancelled` if a cancel was already in flight. */
|
|
@@ -3714,7 +3867,9 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
3714
3867
|
ctx.execOptions.onStepStatus(msg.stepIndex, ctx.stepNames.get(msg.stepIndex) ?? "", msg.status, {
|
|
3715
3868
|
durationMs: msg.durationMs,
|
|
3716
3869
|
...msg.error && { error: msg.error },
|
|
3717
|
-
...msg.secretsAccessed && { secretsAccessed: msg.secretsAccessed }
|
|
3870
|
+
...msg.secretsAccessed && { secretsAccessed: msg.secretsAccessed },
|
|
3871
|
+
...msg.step_type && { step_type: msg.step_type },
|
|
3872
|
+
...msg.data && msg.data
|
|
3718
3873
|
});
|
|
3719
3874
|
return;
|
|
3720
3875
|
case "step.secret_mount":
|
|
@@ -3735,6 +3890,9 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
3735
3890
|
case "agent.api.request":
|
|
3736
3891
|
relayAgentApiRequest(msg, ctx);
|
|
3737
3892
|
return;
|
|
3893
|
+
case "cache.request":
|
|
3894
|
+
relayCacheRequest$1(msg, ctx);
|
|
3895
|
+
return;
|
|
3738
3896
|
case "job.complete":
|
|
3739
3897
|
handleJobComplete(msg, dispatch, ctx);
|
|
3740
3898
|
return;
|
|
@@ -3905,10 +4063,10 @@ var init_fork_runner = __esmMin((() => {
|
|
|
3905
4063
|
* network access. This mode provides credential isolation only and should
|
|
3906
4064
|
* be used in trusted environments.
|
|
3907
4065
|
*/
|
|
3908
|
-
var logger$
|
|
4066
|
+
var logger$4, BareMetalSandbox;
|
|
3909
4067
|
var init_bare_metal_sandbox = __esmMin((() => {
|
|
3910
4068
|
init_fork_runner();
|
|
3911
|
-
logger$
|
|
4069
|
+
logger$4 = createLogger({ prefix: "bare-metal-sandbox" });
|
|
3912
4070
|
BareMetalSandbox = class {
|
|
3913
4071
|
runnerPath;
|
|
3914
4072
|
useBwrap;
|
|
@@ -3935,12 +4093,12 @@ var init_bare_metal_sandbox = __esmMin((() => {
|
|
|
3935
4093
|
if (this.useBwrap) try {
|
|
3936
4094
|
const { execSync } = await import("node:child_process");
|
|
3937
4095
|
execSync("which bwrap", { stdio: "ignore" });
|
|
3938
|
-
if (this.sandboxNetwork === "isolated") logger$
|
|
3939
|
-
else logger$
|
|
4096
|
+
if (this.sandboxNetwork === "isolated") logger$4.info("Bubblewrap (bwrap) sandbox enabled with network isolation (--unshare-net)");
|
|
4097
|
+
else logger$4.info("Bubblewrap (bwrap) sandbox enabled with host network (KICI_SANDBOX_NETWORK=host)");
|
|
3940
4098
|
} catch {
|
|
3941
4099
|
throw new Error("Bubblewrap (bwrap) not found. Install bubblewrap or set sandbox=false. On Debian/Ubuntu: apt install bubblewrap");
|
|
3942
4100
|
}
|
|
3943
|
-
else logger$
|
|
4101
|
+
else logger$4.warn("Bare-metal without sandbox provides limited isolation. Only environment sanitization is active. Enable sandbox=true with bubblewrap for PID/IPC/filesystem namespace isolation.");
|
|
3944
4102
|
}
|
|
3945
4103
|
/**
|
|
3946
4104
|
* Execute a job by forking the workflow runner with sanitized environment.
|
|
@@ -4130,6 +4288,32 @@ function relayApiRequest(stream, options, apiMsg) {
|
|
|
4130
4288
|
} catch {}
|
|
4131
4289
|
}
|
|
4132
4290
|
/**
|
|
4291
|
+
* Relay cache.request from the container runner to the orchestrator via
|
|
4292
|
+
* options.onCacheRequest, then write the response back through `stream`. If
|
|
4293
|
+
* the agent doesn't expose a cache relay, write a structured error response
|
|
4294
|
+
* so the runner doesn't hang.
|
|
4295
|
+
*/
|
|
4296
|
+
function relayCacheRequest(stream, options, cacheMsg) {
|
|
4297
|
+
const writeResponse = (response) => {
|
|
4298
|
+
try {
|
|
4299
|
+
stream.write(JSON.stringify(response) + "\n");
|
|
4300
|
+
} catch {}
|
|
4301
|
+
};
|
|
4302
|
+
if (!options.onCacheRequest) {
|
|
4303
|
+
writeResponse({
|
|
4304
|
+
type: "cache.response",
|
|
4305
|
+
requestId: cacheMsg.requestId,
|
|
4306
|
+
error: "Cache not available in this agent configuration"
|
|
4307
|
+
});
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
options.onCacheRequest(cacheMsg).then((response) => writeResponse(response), (err) => writeResponse({
|
|
4311
|
+
type: "cache.response",
|
|
4312
|
+
requestId: cacheMsg.requestId,
|
|
4313
|
+
error: toErrorMessage(err)
|
|
4314
|
+
}));
|
|
4315
|
+
}
|
|
4316
|
+
/**
|
|
4133
4317
|
* Apply a job.complete message to the mutable runner state: capture status,
|
|
4134
4318
|
* merge any bulk-reported step results, propagate plain outputs, and encrypt
|
|
4135
4319
|
* secret outputs if a run public key is available.
|
|
@@ -4144,14 +4328,14 @@ function applyJobComplete(msg, stepResults, state, options) {
|
|
|
4144
4328
|
if (msg.secretOutputs && options.dispatch.runPublicKey) try {
|
|
4145
4329
|
state.encryptedSecretOutputs = encryptSecretOutputs(msg.secretOutputs, options.dispatch.runPublicKey);
|
|
4146
4330
|
} catch (err) {
|
|
4147
|
-
logger$
|
|
4331
|
+
logger$3.warn("Failed to encrypt secret outputs", { error: toErrorMessage(err) });
|
|
4148
4332
|
}
|
|
4149
4333
|
}
|
|
4150
|
-
var logger$
|
|
4334
|
+
var logger$3, MAX_STDERR_LINES, ABORT_GRACE_MS, CONTAINER_STOP_TIMEOUT, ContainerSandbox;
|
|
4151
4335
|
var init_container_sandbox = __esmMin((() => {
|
|
4152
4336
|
init_fork_runner();
|
|
4153
4337
|
init_secret_encryption();
|
|
4154
|
-
logger$
|
|
4338
|
+
logger$3 = createLogger({ prefix: "container-sandbox" });
|
|
4155
4339
|
MAX_STDERR_LINES = 20;
|
|
4156
4340
|
ABORT_GRACE_MS = 1e4;
|
|
4157
4341
|
CONTAINER_STOP_TIMEOUT = 10;
|
|
@@ -4183,7 +4367,7 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4183
4367
|
async setup(options) {
|
|
4184
4368
|
this.containerName = `kici-sandbox-${this.jobId}-${Date.now()}`;
|
|
4185
4369
|
const envArray = Object.entries(this.env).map(([k, v]) => `${k}=${v}`);
|
|
4186
|
-
logger$
|
|
4370
|
+
logger$3.info("Creating sandbox container", {
|
|
4187
4371
|
name: this.containerName,
|
|
4188
4372
|
image: this.image,
|
|
4189
4373
|
workDir: options.workDir
|
|
@@ -4201,7 +4385,7 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4201
4385
|
HostConfig: { Binds: [`${options.workDir}:/workspace`, `${this.runnerPath}:${this.runnerMountPath}:ro`] }
|
|
4202
4386
|
});
|
|
4203
4387
|
await this.container.start();
|
|
4204
|
-
logger$
|
|
4388
|
+
logger$3.info("Sandbox container started", {
|
|
4205
4389
|
name: this.containerName,
|
|
4206
4390
|
containerId: this.container.id.slice(0, 12)
|
|
4207
4391
|
});
|
|
@@ -4214,7 +4398,7 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4214
4398
|
try {
|
|
4215
4399
|
outcome = await this.awaitJobCompletion(streamCtx, options);
|
|
4216
4400
|
} catch (err) {
|
|
4217
|
-
logger$
|
|
4401
|
+
logger$3.error("Job execution error", {
|
|
4218
4402
|
error: toErrorMessage(err),
|
|
4219
4403
|
stderrTail: streamCtx.stderrLines.slice(-5).join("\n")
|
|
4220
4404
|
});
|
|
@@ -4264,7 +4448,7 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4264
4448
|
});
|
|
4265
4449
|
const abortHandler = () => {
|
|
4266
4450
|
this.handleAbort().catch((err) => {
|
|
4267
|
-
logger$
|
|
4451
|
+
logger$3.warn("Error during abort", { error: toErrorMessage(err) });
|
|
4268
4452
|
});
|
|
4269
4453
|
};
|
|
4270
4454
|
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
@@ -4301,7 +4485,7 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4301
4485
|
try {
|
|
4302
4486
|
msg = JSON.parse(line);
|
|
4303
4487
|
} catch {
|
|
4304
|
-
logger$
|
|
4488
|
+
logger$3.warn("Non-JSON output from runner", { line: line.slice(0, 200) });
|
|
4305
4489
|
return;
|
|
4306
4490
|
}
|
|
4307
4491
|
if (this.dispatchRunnerMessage(msg, stream, options, stepNames, stepResults, state)) resolve({
|
|
@@ -4349,7 +4533,9 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4349
4533
|
options.onStepStatus(msg.stepIndex, name, msg.status, {
|
|
4350
4534
|
durationMs: msg.durationMs,
|
|
4351
4535
|
...msg.error && { error: msg.error },
|
|
4352
|
-
...msg.secretsAccessed && { secretsAccessed: msg.secretsAccessed }
|
|
4536
|
+
...msg.secretsAccessed && { secretsAccessed: msg.secretsAccessed },
|
|
4537
|
+
...msg.step_type && { step_type: msg.step_type },
|
|
4538
|
+
...msg.data && msg.data
|
|
4353
4539
|
});
|
|
4354
4540
|
stepResults.push({
|
|
4355
4541
|
name,
|
|
@@ -4381,11 +4567,14 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4381
4567
|
case "agent.api.request":
|
|
4382
4568
|
relayApiRequest(stream, options, msg);
|
|
4383
4569
|
return false;
|
|
4570
|
+
case "cache.request":
|
|
4571
|
+
relayCacheRequest(stream, options, msg);
|
|
4572
|
+
return false;
|
|
4384
4573
|
case "job.complete":
|
|
4385
4574
|
applyJobComplete(msg, stepResults, state, options);
|
|
4386
4575
|
return true;
|
|
4387
4576
|
default:
|
|
4388
|
-
logger$
|
|
4577
|
+
logger$3.warn("Unrecognized IPC message from container runner", { type: msg.type });
|
|
4389
4578
|
return false;
|
|
4390
4579
|
}
|
|
4391
4580
|
}
|
|
@@ -4412,14 +4601,14 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4412
4601
|
async teardown() {
|
|
4413
4602
|
if (!this.container) return;
|
|
4414
4603
|
if (this.keepFailed && this.jobFailed) {
|
|
4415
|
-
logger$
|
|
4604
|
+
logger$3.info("Keeping failed container for debugging", {
|
|
4416
4605
|
name: this.containerName,
|
|
4417
4606
|
containerId: this.container.id.slice(0, 12)
|
|
4418
4607
|
});
|
|
4419
4608
|
this.container = null;
|
|
4420
4609
|
return;
|
|
4421
4610
|
}
|
|
4422
|
-
logger$
|
|
4611
|
+
logger$3.info("Tearing down sandbox container", { name: this.containerName });
|
|
4423
4612
|
try {
|
|
4424
4613
|
await this.container.stop({ t: CONTAINER_STOP_TIMEOUT });
|
|
4425
4614
|
} catch {}
|
|
@@ -4450,7 +4639,7 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4450
4639
|
*/
|
|
4451
4640
|
async handleAbort() {
|
|
4452
4641
|
if (!this.execStream && !this.container) return;
|
|
4453
|
-
logger$
|
|
4642
|
+
logger$3.info("Aborting sandbox execution", { name: this.containerName });
|
|
4454
4643
|
if (this.execStream) try {
|
|
4455
4644
|
this.execStream.write(JSON.stringify({ type: "abort" }) + "\n");
|
|
4456
4645
|
} catch {}
|
|
@@ -4519,7 +4708,7 @@ function determineExecutionMode(jobConfig, agentConfig) {
|
|
|
4519
4708
|
if (agentConfig.scalerManaged) return "firecracker";
|
|
4520
4709
|
return "bare-metal";
|
|
4521
4710
|
}
|
|
4522
|
-
var logger$
|
|
4711
|
+
var logger$2, JobRunner$1;
|
|
4523
4712
|
var init_job_runner = __esmMin((() => {
|
|
4524
4713
|
init_git_clone();
|
|
4525
4714
|
init_workflow_loader();
|
|
@@ -4536,7 +4725,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4536
4725
|
init_download();
|
|
4537
4726
|
init_sandbox();
|
|
4538
4727
|
init_prometheus();
|
|
4539
|
-
logger$
|
|
4728
|
+
logger$2 = createLogger({ prefix: "job-runner" });
|
|
4540
4729
|
JobRunner$1 = class {
|
|
4541
4730
|
send;
|
|
4542
4731
|
sendDirect;
|
|
@@ -4550,6 +4739,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4550
4739
|
_sendRunEvent;
|
|
4551
4740
|
_sendConcurrencyReport;
|
|
4552
4741
|
_sendApiRequest;
|
|
4742
|
+
_requestUserCache;
|
|
4553
4743
|
/** Tracks running jobs for concurrency and cancellation */
|
|
4554
4744
|
activeJobs = /* @__PURE__ */ new Map();
|
|
4555
4745
|
/** Active sandbox for the current job (used for abort). */
|
|
@@ -4567,6 +4757,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4567
4757
|
this._sendRunEvent = deps.sendRunEvent;
|
|
4568
4758
|
this._sendConcurrencyReport = deps.sendConcurrencyReport;
|
|
4569
4759
|
this._sendApiRequest = deps.sendApiRequest;
|
|
4760
|
+
this._requestUserCache = deps.requestUserCache;
|
|
4570
4761
|
}
|
|
4571
4762
|
/**
|
|
4572
4763
|
* Execute a dispatched job through its full lifecycle.
|
|
@@ -4634,7 +4825,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4634
4825
|
}
|
|
4635
4826
|
if (jobConfig.buildOnly === true) {
|
|
4636
4827
|
if (jobConfig.fullRepo) {
|
|
4637
|
-
logger$
|
|
4828
|
+
logger$2.warn("Build job received for fullRepo run -- skipping (should not happen)", {
|
|
4638
4829
|
jobId,
|
|
4639
4830
|
runId
|
|
4640
4831
|
});
|
|
@@ -4656,7 +4847,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4656
4847
|
async executeStandardJob(dispatch, workDir, abortController) {
|
|
4657
4848
|
const { runId, jobId } = dispatch;
|
|
4658
4849
|
const ctx = getRequestContext();
|
|
4659
|
-
logger$
|
|
4850
|
+
logger$2.info(`Run: ${ctx.runId ?? runId} | Trace: ${ctx.requestId ?? "N/A"}`);
|
|
4660
4851
|
this.sendJobStatus(dispatch, ExecutionJobStatus.enum.running);
|
|
4661
4852
|
const heartbeatTimer = setInterval(() => {
|
|
4662
4853
|
this.send({
|
|
@@ -4681,7 +4872,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4681
4872
|
if (sandbox) {
|
|
4682
4873
|
this.emitRunEvent(runId, "agent.teardown", { jobId });
|
|
4683
4874
|
await sandbox.teardown().catch((err) => {
|
|
4684
|
-
logger$
|
|
4875
|
+
logger$2.warn("Sandbox teardown error", { error: toErrorMessage(err) });
|
|
4685
4876
|
});
|
|
4686
4877
|
}
|
|
4687
4878
|
}
|
|
@@ -4709,7 +4900,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4709
4900
|
environmentVars: typedConfig.environmentVars ?? void 0,
|
|
4710
4901
|
jobEnv: typedConfig.jobEnv ?? void 0
|
|
4711
4902
|
});
|
|
4712
|
-
logger$
|
|
4903
|
+
logger$2.info("Creating execution sandbox", {
|
|
4713
4904
|
executionMode,
|
|
4714
4905
|
jobId,
|
|
4715
4906
|
runnerPath
|
|
@@ -4780,6 +4971,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4780
4971
|
onStepStatus: (stepIndex, stepName, state, data) => {
|
|
4781
4972
|
let logBytesStreamed;
|
|
4782
4973
|
if (state === ExecutionStepStatus.enum.success || state === ExecutionStepStatus.enum.failed || state === ExecutionStepStatus.enum.skipped) logBytesStreamed = logStreamers.get(stepIndex)?.getTotalBytes() ?? 0;
|
|
4974
|
+
this.maybeEmitCacheRunEvent(runId, jobId, stepIndex, state, data);
|
|
4783
4975
|
this.sendStepStatus(dispatch, stepIndex, stepName, state, data, logBytesStreamed);
|
|
4784
4976
|
},
|
|
4785
4977
|
onLogLine: (stepIndex, line) => {
|
|
@@ -4804,6 +4996,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4804
4996
|
};
|
|
4805
4997
|
},
|
|
4806
4998
|
onApiRequest: this._sendApiRequest ? async (method, params) => this._sendApiRequest(method, params) : void 0,
|
|
4999
|
+
onCacheRequest: this._requestUserCache ? async (request) => this._requestUserCache(jobId, request) : void 0,
|
|
4807
5000
|
onSecretMount: (event) => {
|
|
4808
5001
|
this.emitRunEvent(runId, "step.secret_mount", {
|
|
4809
5002
|
jobId,
|
|
@@ -4837,7 +5030,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4837
5030
|
stepsTotal.add(1, { status: stepResult.status });
|
|
4838
5031
|
if (stepResult.durationMs > 0) stepDurationSeconds.record(stepResult.durationMs / 1e3);
|
|
4839
5032
|
}
|
|
4840
|
-
if (result.status === ExecutionJobStatus.enum.failed) logger$
|
|
5033
|
+
if (result.status === ExecutionJobStatus.enum.failed) logger$2.error("Sandbox returned failed result", {
|
|
4841
5034
|
durationMs: result.durationMs,
|
|
4842
5035
|
stepCount: result.stepResults.length,
|
|
4843
5036
|
steps: result.stepResults.map((r) => `${r.name}:${r.status}`).join(","),
|
|
@@ -4866,7 +5059,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4866
5059
|
async handleBuildJob(dispatch, workDir, abortController) {
|
|
4867
5060
|
const { runId, jobId, jobConfig } = dispatch;
|
|
4868
5061
|
const buildCtx = getRequestContext();
|
|
4869
|
-
logger$
|
|
5062
|
+
logger$2.info(`Run: ${buildCtx.runId ?? runId} | Trace: ${buildCtx.requestId ?? "N/A"}`);
|
|
4870
5063
|
this.sendJobStatus(dispatch, ExecutionJobStatus.enum.running);
|
|
4871
5064
|
const buildStreamer = this.createStepStreamer(dispatch, 0);
|
|
4872
5065
|
const buildLog = (msg) => buildStreamer.addLine(msg);
|
|
@@ -4955,14 +5148,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
4955
5148
|
const cliPublicKey = jobConfig.cliPublicKey;
|
|
4956
5149
|
const orchestratorPrivateKey = jobConfig.orchestratorPrivateKey;
|
|
4957
5150
|
if (tarballUrl && cliPublicKey && orchestratorPrivateKey) {
|
|
4958
|
-
logger$
|
|
5151
|
+
logger$2.info("Applying overlay tarball for test run", { jobId });
|
|
4959
5152
|
const overlayResult = await applyOverlay({
|
|
4960
5153
|
tarballUrl,
|
|
4961
5154
|
cliPublicKey,
|
|
4962
5155
|
orchestratorPrivateKey,
|
|
4963
5156
|
repoDir: workDir
|
|
4964
5157
|
});
|
|
4965
|
-
logger$
|
|
5158
|
+
logger$2.info("Overlay applied", {
|
|
4966
5159
|
filesApplied: overlayResult.filesApplied,
|
|
4967
5160
|
filesDeleted: overlayResult.filesDeleted
|
|
4968
5161
|
});
|
|
@@ -4990,9 +5183,9 @@ var init_job_runner = __esmMin((() => {
|
|
|
4990
5183
|
platform: os.platform(),
|
|
4991
5184
|
arch: os.arch()
|
|
4992
5185
|
};
|
|
4993
|
-
logger$
|
|
5186
|
+
logger$2.info("Requesting dep upload URL from orchestrator", { lockfileHash: buildConfig.lockfileHash });
|
|
4994
5187
|
const depUploadUrl = await this.requestUploadUrl(dispatch.jobId, "deps", depKey);
|
|
4995
|
-
logger$
|
|
5188
|
+
logger$2.info("Uploading dep tarball to S3", {
|
|
4996
5189
|
size: tarball.length,
|
|
4997
5190
|
hash: hash.slice(0, 12)
|
|
4998
5191
|
});
|
|
@@ -5001,7 +5194,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5001
5194
|
...depKey,
|
|
5002
5195
|
depsHash: hash
|
|
5003
5196
|
});
|
|
5004
|
-
logger$
|
|
5197
|
+
logger$2.info("Dep tarball upload complete", { lockfileHash: buildConfig.lockfileHash });
|
|
5005
5198
|
buildLog(`Deps tarball uploaded (${tarball.length} bytes)`);
|
|
5006
5199
|
this.sendJobStatus(dispatch, ExecutionJobStatus.enum.running, {
|
|
5007
5200
|
buildEvent: "deps_packed",
|
|
@@ -5027,15 +5220,15 @@ var init_job_runner = __esmMin((() => {
|
|
|
5027
5220
|
platform: os.platform(),
|
|
5028
5221
|
arch: os.arch()
|
|
5029
5222
|
};
|
|
5030
|
-
logger$
|
|
5223
|
+
logger$2.info("Requesting source tarball upload URL from orchestrator", { contentHash: buildConfig.contentHash });
|
|
5031
5224
|
const sourceUploadUrl = await this.requestUploadUrl(dispatch.jobId, "source", sourceKey);
|
|
5032
|
-
logger$
|
|
5225
|
+
logger$2.info("Uploading source tarball to S3", {
|
|
5033
5226
|
size: tarball.length,
|
|
5034
5227
|
contentHash: buildConfig.contentHash
|
|
5035
5228
|
});
|
|
5036
5229
|
await uploadToPresignedUrl(sourceUploadUrl, tarball);
|
|
5037
5230
|
this.sendUploadComplete(dispatch.jobId, "source", sourceKey);
|
|
5038
|
-
logger$
|
|
5231
|
+
logger$2.info("Source tarball upload complete", { contentHash: buildConfig.contentHash });
|
|
5039
5232
|
buildLog(`Source tarball packed and uploaded (${tarball.length} bytes, hash: ${buildConfig.contentHash.slice(0, 12)})`);
|
|
5040
5233
|
this.sendJobStatus(dispatch, ExecutionJobStatus.enum.running, {
|
|
5041
5234
|
buildEvent: "source_packed",
|
|
@@ -5058,7 +5251,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5058
5251
|
async handleInitJob(dispatch, workDir, abortController) {
|
|
5059
5252
|
const { runId, jobId, jobConfig } = dispatch;
|
|
5060
5253
|
const config = jobConfig;
|
|
5061
|
-
logger$
|
|
5254
|
+
logger$2.info("Starting init job", {
|
|
5062
5255
|
jobId,
|
|
5063
5256
|
targetJobName: config.targetJobName,
|
|
5064
5257
|
workflowName: config.workflowName
|
|
@@ -5106,7 +5299,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5106
5299
|
}
|
|
5107
5300
|
const kiciDir = join(workDir, ".kici");
|
|
5108
5301
|
const hasPackage = await fileExists(join(kiciDir, "package.json"));
|
|
5109
|
-
logger$
|
|
5302
|
+
logger$2.info("Init job: checking deps", {
|
|
5110
5303
|
kiciDir,
|
|
5111
5304
|
hasPackageJson: hasPackage,
|
|
5112
5305
|
source: config.source
|
|
@@ -5129,7 +5322,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5129
5322
|
dynamicConcurrencyGroup: config.dynamicConcurrencyGroup
|
|
5130
5323
|
}, config.timeoutMs);
|
|
5131
5324
|
});
|
|
5132
|
-
logger$
|
|
5325
|
+
logger$2.info("Init job completed successfully", {
|
|
5133
5326
|
jobId,
|
|
5134
5327
|
hasEnvironment: initResult.environmentName !== void 0,
|
|
5135
5328
|
hasEnv: initResult.env !== void 0,
|
|
@@ -5145,7 +5338,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5145
5338
|
});
|
|
5146
5339
|
} catch (err) {
|
|
5147
5340
|
const errorMsg = toErrorMessage(err);
|
|
5148
|
-
logger$
|
|
5341
|
+
logger$2.error("Init job failed", {
|
|
5149
5342
|
jobId,
|
|
5150
5343
|
error: errorMsg
|
|
5151
5344
|
});
|
|
@@ -5172,7 +5365,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5172
5365
|
const { runId, jobId, jobConfig } = dispatch;
|
|
5173
5366
|
const config = jobConfig;
|
|
5174
5367
|
const timeoutMs = config.timeoutMs ?? 12e4;
|
|
5175
|
-
logger$
|
|
5368
|
+
logger$2.info("Starting DynamicJobFn evaluation", {
|
|
5176
5369
|
jobId,
|
|
5177
5370
|
workflowName: config.workflowName,
|
|
5178
5371
|
sourceIndex: config.source.index
|
|
@@ -5273,7 +5466,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5273
5466
|
workflowName: config.workflowName
|
|
5274
5467
|
});
|
|
5275
5468
|
});
|
|
5276
|
-
logger$
|
|
5469
|
+
logger$2.info("DynamicJobFn evaluation completed", {
|
|
5277
5470
|
jobId,
|
|
5278
5471
|
generatedJobCount: lockJobs.length,
|
|
5279
5472
|
jobNames: lockJobs.map((j) => j.name)
|
|
@@ -5288,7 +5481,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5288
5481
|
});
|
|
5289
5482
|
} catch (err) {
|
|
5290
5483
|
const errorMsg = toErrorMessage(err);
|
|
5291
|
-
logger$
|
|
5484
|
+
logger$2.error("DynamicJobFn evaluation failed", {
|
|
5292
5485
|
jobId,
|
|
5293
5486
|
error: errorMsg
|
|
5294
5487
|
});
|
|
@@ -5296,10 +5489,17 @@ var init_job_runner = __esmMin((() => {
|
|
|
5296
5489
|
await evalStreamer.flush();
|
|
5297
5490
|
evalStreamer.destroy();
|
|
5298
5491
|
this.sendStepStatus(dispatch, 0, "evaluate", ExecutionStepStatus.enum.failed, { error: errorMsg }, evalStreamer.getTotalBytes());
|
|
5299
|
-
|
|
5492
|
+
const dynamicData = {
|
|
5300
5493
|
error: errorMsg,
|
|
5301
5494
|
dynamicFailed: true
|
|
5302
|
-
}
|
|
5495
|
+
};
|
|
5496
|
+
if (err instanceof MatrixExpansionError) dynamicData.initFailure = {
|
|
5497
|
+
scope: "job",
|
|
5498
|
+
category: InitFailureCategory.enum.matrix_expansion,
|
|
5499
|
+
message: errorMsg,
|
|
5500
|
+
jobName: err.jobName
|
|
5501
|
+
};
|
|
5502
|
+
this.sendJobStatus(dispatch, ExecutionJobStatus.enum.failed, dynamicData);
|
|
5303
5503
|
} finally {
|
|
5304
5504
|
clearInterval(heartbeatTimer);
|
|
5305
5505
|
}
|
|
@@ -5361,6 +5561,31 @@ var init_job_runner = __esmMin((() => {
|
|
|
5361
5561
|
this._sendRunEvent(runId, eventType, opts);
|
|
5362
5562
|
}
|
|
5363
5563
|
/**
|
|
5564
|
+
* Emit a `cache.restore` / `cache.save` run event for a cache pseudo-step.
|
|
5565
|
+
*
|
|
5566
|
+
* The cache phase tags its `step.complete` IPC with a {@link CacheStepType}
|
|
5567
|
+
* `step_type` and a `data.cacheOutcome` ({@link CacheOutcome}); when one of
|
|
5568
|
+
* those terminal pseudo-step statuses arrives here, mirror it onto the run
|
|
5569
|
+
* timeline as a `run.event` so hit/miss/saved/skipped/error is recorded for
|
|
5570
|
+
* the dashboard. A no-op for regular steps and hooks.
|
|
5571
|
+
*/
|
|
5572
|
+
maybeEmitCacheRunEvent(runId, jobId, stepIndex, state, data) {
|
|
5573
|
+
if (state === ExecutionStepStatus.enum.running) return;
|
|
5574
|
+
const stepType = data?.step_type;
|
|
5575
|
+
const eventType = stepType === CacheStepType.enum["cache:restore"] ? CacheRunEventType.enum["cache.restore"] : stepType === CacheStepType.enum["cache:save"] ? CacheRunEventType.enum["cache.save"] : void 0;
|
|
5576
|
+
if (!eventType) return;
|
|
5577
|
+
this.emitRunEvent(runId, eventType, {
|
|
5578
|
+
jobId,
|
|
5579
|
+
metadata: {
|
|
5580
|
+
stepIndex,
|
|
5581
|
+
...data?.cacheOutcome !== void 0 && { outcome: data.cacheOutcome },
|
|
5582
|
+
...data?.key !== void 0 && { key: data.key },
|
|
5583
|
+
...data?.matchedKey !== void 0 && { matchedKey: data.matchedKey },
|
|
5584
|
+
...data?.bytes !== void 0 && { bytes: data.bytes }
|
|
5585
|
+
}
|
|
5586
|
+
});
|
|
5587
|
+
}
|
|
5588
|
+
/**
|
|
5364
5589
|
* Create a LogStreamer for a synthetic step (build, evaluate, etc.).
|
|
5365
5590
|
*/
|
|
5366
5591
|
createStepStreamer(dispatch, stepIndex) {
|
|
@@ -5444,14 +5669,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
5444
5669
|
*/
|
|
5445
5670
|
init_console_capture();
|
|
5446
5671
|
init_npm_resolver();
|
|
5447
|
-
const AGENT_VERSION = "0.1.
|
|
5448
|
-
const BUILD_COMMIT = "
|
|
5449
|
-
const SDK_VERSION = "0.1.
|
|
5450
|
-
const SDK_BUNDLE_HASH = "
|
|
5451
|
-
const SHARED_VERSION = "0.1.
|
|
5452
|
-
const SHARED_BUNDLE_HASH = "
|
|
5453
|
-
const ENGINE_VERSION = "0.1.
|
|
5454
|
-
const ENGINE_BUNDLE_HASH = "
|
|
5672
|
+
const AGENT_VERSION = "0.1.15";
|
|
5673
|
+
const BUILD_COMMIT = "831f6a763";
|
|
5674
|
+
const SDK_VERSION = "0.1.15";
|
|
5675
|
+
const SDK_BUNDLE_HASH = "cee24b67bbb4a1d2a770e4063dd37a9af33409faeb5ce32f936703b4431316dd";
|
|
5676
|
+
const SHARED_VERSION = "0.1.15";
|
|
5677
|
+
const SHARED_BUNDLE_HASH = "e82a8b68a7d72698674352158c5de4c0d10824c61bfa7166cc8f846044b8b5e4";
|
|
5678
|
+
const ENGINE_VERSION = "0.1.15";
|
|
5679
|
+
const ENGINE_BUNDLE_HASH = "032d6b28b3d32bcbea80ba80a52934b017ee2c86a97fcaf8fe8db0d86d72a7f7";
|
|
5455
5680
|
initTelemetry({
|
|
5456
5681
|
serviceName: "kici-agent",
|
|
5457
5682
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
@@ -5459,18 +5684,19 @@ initTelemetry({
|
|
|
5459
5684
|
const { connectionStatus, jobsActive, jobsTotal } = await Promise.resolve().then(() => (init_prometheus(), prometheus_exports));
|
|
5460
5685
|
const { JobRunner } = await Promise.resolve().then(() => (init_job_runner(), job_runner_exports));
|
|
5461
5686
|
setServiceName("agent");
|
|
5462
|
-
const logger = createLogger({ prefix: "agent" });
|
|
5687
|
+
const logger$1 = createLogger({ prefix: "agent" });
|
|
5463
5688
|
installConsoleCapture();
|
|
5464
|
-
await guardStartup(logger, async () => {
|
|
5689
|
+
await guardStartup(logger$1, async () => {
|
|
5465
5690
|
const config = loadConfig();
|
|
5466
|
-
logger.info("Agent starting", {
|
|
5691
|
+
logger$1.info("Agent starting", {
|
|
5467
5692
|
agentId: config.agentId,
|
|
5468
5693
|
orchestratorUrl: config.orchestratorUrl,
|
|
5469
5694
|
labels: config.labels,
|
|
5470
5695
|
roles: config.roles,
|
|
5471
5696
|
port: config.port
|
|
5472
5697
|
});
|
|
5473
|
-
|
|
5698
|
+
gcStaleAgentTmpDirs();
|
|
5699
|
+
logger$1.info("agent.build.info", {
|
|
5474
5700
|
agentVersion: AGENT_VERSION,
|
|
5475
5701
|
buildCommit: BUILD_COMMIT,
|
|
5476
5702
|
sdkVersion: SDK_VERSION,
|
|
@@ -5492,7 +5718,7 @@ await guardStartup(logger, async () => {
|
|
|
5492
5718
|
if (toolErrors.length > 0) throw new Error("Agent required-tools validation failed:\n" + toolErrors.map((e) => ` - ${e}`).join("\n"));
|
|
5493
5719
|
if (config.roles === void 0 || config.roles.includes("builder")) {
|
|
5494
5720
|
const npmVersion = verifyNpmAvailable();
|
|
5495
|
-
logger.info("Builder role: npm verified", { npmVersion });
|
|
5721
|
+
logger$1.info("Builder role: npm verified", { npmVersion });
|
|
5496
5722
|
}
|
|
5497
5723
|
let isDraining = false;
|
|
5498
5724
|
let idleShutdownTimer;
|
|
@@ -5509,7 +5735,8 @@ await guardStartup(logger, async () => {
|
|
|
5509
5735
|
sendJobContext: (runId, jobId, context) => client.sendJobContext(runId, jobId, context),
|
|
5510
5736
|
sendRunEvent: (runId, eventType, opts) => client.sendRunEvent(runId, eventType, opts),
|
|
5511
5737
|
sendConcurrencyReport: (runId, jobId, group) => client.sendConcurrencyReport(runId, jobId, group),
|
|
5512
|
-
sendApiRequest: (method, params) => client.sendApiRequest(method, params ?? {})
|
|
5738
|
+
sendApiRequest: (method, params) => client.sendApiRequest(method, params ?? {}),
|
|
5739
|
+
requestUserCache: (jobId, request) => client.requestUserCache(jobId, request)
|
|
5513
5740
|
});
|
|
5514
5741
|
/** Build and send an agent.status message with dynamic OS metadata. */
|
|
5515
5742
|
function sendAgentStatus() {
|
|
@@ -5533,15 +5760,31 @@ await guardStartup(logger, async () => {
|
|
|
5533
5760
|
jobId: dispatch.jobId
|
|
5534
5761
|
}, () => {
|
|
5535
5762
|
if (isDraining) {
|
|
5536
|
-
logger.info("Draining: rejecting job dispatch", { jobId: dispatch.jobId });
|
|
5763
|
+
logger$1.info("Draining: rejecting job dispatch", { jobId: dispatch.jobId });
|
|
5764
|
+
client.sendDirect({
|
|
5765
|
+
type: "job.reject",
|
|
5766
|
+
messageId: randomUUID(),
|
|
5767
|
+
runId: dispatch.runId,
|
|
5768
|
+
jobId: dispatch.jobId,
|
|
5769
|
+
reason: "draining",
|
|
5770
|
+
timestamp: Date.now()
|
|
5771
|
+
});
|
|
5537
5772
|
sendAgentStatus();
|
|
5538
5773
|
return;
|
|
5539
5774
|
}
|
|
5540
5775
|
if (jobRunner.activeJobs.size > 0) {
|
|
5541
|
-
logger.warn("Already running a job,
|
|
5776
|
+
logger$1.warn("Already running a job, rejecting dispatch", {
|
|
5542
5777
|
jobId: dispatch.jobId,
|
|
5543
5778
|
activeJobs: jobRunner.activeJobs.size
|
|
5544
5779
|
});
|
|
5780
|
+
client.sendDirect({
|
|
5781
|
+
type: "job.reject",
|
|
5782
|
+
messageId: randomUUID(),
|
|
5783
|
+
runId: dispatch.runId,
|
|
5784
|
+
jobId: dispatch.jobId,
|
|
5785
|
+
reason: "busy",
|
|
5786
|
+
timestamp: Date.now()
|
|
5787
|
+
});
|
|
5545
5788
|
sendAgentStatus();
|
|
5546
5789
|
return;
|
|
5547
5790
|
}
|
|
@@ -5549,7 +5792,14 @@ await guardStartup(logger, async () => {
|
|
|
5549
5792
|
clearTimeout(idleShutdownTimer);
|
|
5550
5793
|
idleShutdownTimer = void 0;
|
|
5551
5794
|
}
|
|
5552
|
-
|
|
5795
|
+
client.sendDirect({
|
|
5796
|
+
type: "job.ack",
|
|
5797
|
+
messageId: randomUUID(),
|
|
5798
|
+
runId: dispatch.runId,
|
|
5799
|
+
jobId: dispatch.jobId,
|
|
5800
|
+
timestamp: Date.now()
|
|
5801
|
+
});
|
|
5802
|
+
logger$1.info("Accepting job dispatch", {
|
|
5553
5803
|
jobId: dispatch.jobId,
|
|
5554
5804
|
runId: dispatch.runId,
|
|
5555
5805
|
activeJobs: jobRunner.activeJobs.size + 1
|
|
@@ -5558,7 +5808,7 @@ await guardStartup(logger, async () => {
|
|
|
5558
5808
|
jobRunner.execute(dispatch).then(() => {
|
|
5559
5809
|
jobsTotal.add(1, { status: "success" });
|
|
5560
5810
|
}).catch((err) => {
|
|
5561
|
-
logger.error("Job execution error", {
|
|
5811
|
+
logger$1.error("Job execution error", {
|
|
5562
5812
|
jobId: dispatch.jobId,
|
|
5563
5813
|
error: toErrorMessage(err)
|
|
5564
5814
|
});
|
|
@@ -5569,7 +5819,7 @@ await guardStartup(logger, async () => {
|
|
|
5569
5819
|
sendAgentStatus();
|
|
5570
5820
|
if (config.scalerManaged && jobRunner.activeJobs.size === 0) {
|
|
5571
5821
|
if (client.state !== "registered") {
|
|
5572
|
-
logger.info("Scaler-managed agent idle but disconnected, deferring shutdown until reconnected");
|
|
5822
|
+
logger$1.info("Scaler-managed agent idle but disconnected, deferring shutdown until reconnected");
|
|
5573
5823
|
return;
|
|
5574
5824
|
}
|
|
5575
5825
|
startIdleShutdownTimer();
|
|
@@ -5578,7 +5828,7 @@ await guardStartup(logger, async () => {
|
|
|
5578
5828
|
});
|
|
5579
5829
|
},
|
|
5580
5830
|
onJobCancel: (cancel) => {
|
|
5581
|
-
logger.info("Job cancel received", {
|
|
5831
|
+
logger$1.info("Job cancel received", {
|
|
5582
5832
|
jobId: cancel.jobId,
|
|
5583
5833
|
reason: cancel.reason
|
|
5584
5834
|
});
|
|
@@ -5596,13 +5846,13 @@ await guardStartup(logger, async () => {
|
|
|
5596
5846
|
const idleMs = config.scalerIdleTimeoutMs;
|
|
5597
5847
|
if (idleShutdownTimer) clearTimeout(idleShutdownTimer);
|
|
5598
5848
|
if (idleMs <= 0) {
|
|
5599
|
-
logger.info("Scaler-managed agent idle after job completion, shutting down");
|
|
5849
|
+
logger$1.info("Scaler-managed agent idle after job completion, shutting down");
|
|
5600
5850
|
gracefulShutdown("scaler-idle");
|
|
5601
5851
|
} else {
|
|
5602
|
-
logger.info(`Scaler-managed agent idle, waiting ${idleMs}ms for follow-up jobs`);
|
|
5852
|
+
logger$1.info(`Scaler-managed agent idle, waiting ${idleMs}ms for follow-up jobs`);
|
|
5603
5853
|
idleShutdownTimer = setTimeout(() => {
|
|
5604
5854
|
if (jobRunner.activeJobs.size === 0) {
|
|
5605
|
-
logger.info("Scaler-managed agent still idle after timeout, shutting down");
|
|
5855
|
+
logger$1.info("Scaler-managed agent still idle after timeout, shutting down");
|
|
5606
5856
|
gracefulShutdown("scaler-idle");
|
|
5607
5857
|
}
|
|
5608
5858
|
}, idleMs);
|
|
@@ -5612,17 +5862,17 @@ await guardStartup(logger, async () => {
|
|
|
5612
5862
|
if (!config.scalerManaged || jobRunner.activeJobs.size > 0) return;
|
|
5613
5863
|
if (pendingDispatch) {
|
|
5614
5864
|
const safetyMs = config.scalerPendingDispatchTimeoutMs;
|
|
5615
|
-
logger.info(`Scaler-managed agent registered with pending bound dispatch, deferring idle shutdown for ${safetyMs}ms`);
|
|
5865
|
+
logger$1.info(`Scaler-managed agent registered with pending bound dispatch, deferring idle shutdown for ${safetyMs}ms`);
|
|
5616
5866
|
if (idleShutdownTimer) clearTimeout(idleShutdownTimer);
|
|
5617
5867
|
idleShutdownTimer = setTimeout(() => {
|
|
5618
5868
|
if (jobRunner.activeJobs.size === 0) {
|
|
5619
|
-
logger.warn("Scaler-managed agent pending-dispatch safety timeout exceeded, shutting down");
|
|
5869
|
+
logger$1.warn("Scaler-managed agent pending-dispatch safety timeout exceeded, shutting down");
|
|
5620
5870
|
gracefulShutdown("scaler-pending-dispatch-timeout");
|
|
5621
5871
|
}
|
|
5622
5872
|
}, safetyMs);
|
|
5623
5873
|
return;
|
|
5624
5874
|
}
|
|
5625
|
-
logger.info("Scaler-managed agent reconnected and idle, starting idle shutdown timer");
|
|
5875
|
+
logger$1.info("Scaler-managed agent reconnected and idle, starting idle shutdown timer");
|
|
5626
5876
|
startIdleShutdownTimer();
|
|
5627
5877
|
};
|
|
5628
5878
|
const wsTransport = new winston.transports.Stream({
|
|
@@ -5632,14 +5882,14 @@ await guardStartup(logger, async () => {
|
|
|
5632
5882
|
} }),
|
|
5633
5883
|
format: winston.format.combine(winston.format.timestamp(), winston.format.json())
|
|
5634
5884
|
});
|
|
5635
|
-
logger.add(wsTransport);
|
|
5885
|
+
logger$1.add(wsTransport);
|
|
5636
5886
|
const envProbes = {};
|
|
5637
5887
|
for (const [k, v] of Object.entries(process.env)) {
|
|
5638
5888
|
if (v === void 0) continue;
|
|
5639
5889
|
if (!/_ENV_PROBE$|_ENV_PROBE_/.test(k)) continue;
|
|
5640
5890
|
envProbes[k] = v.length <= 64 ? v : `${v.slice(0, 61)}...`;
|
|
5641
5891
|
}
|
|
5642
|
-
if (Object.keys(envProbes).length > 0) logger.info("Agent startup env probes (diagnostic)", envProbes);
|
|
5892
|
+
if (Object.keys(envProbes).length > 0) logger$1.info("Agent startup env probes (diagnostic)", envProbes);
|
|
5643
5893
|
client.connect();
|
|
5644
5894
|
connectionStatus.add(1);
|
|
5645
5895
|
const metricsReporter = new MetricsReporter({
|
|
@@ -5679,13 +5929,13 @@ await guardStartup(logger, async () => {
|
|
|
5679
5929
|
fetch: app.fetch,
|
|
5680
5930
|
port: config.port
|
|
5681
5931
|
}, (info) => {
|
|
5682
|
-
logger.info(`Agent started on port ${info.port}`, {
|
|
5932
|
+
logger$1.info(`Agent started on port ${info.port}`, {
|
|
5683
5933
|
port: info.port,
|
|
5684
5934
|
agentId: config.agentId
|
|
5685
5935
|
});
|
|
5686
5936
|
});
|
|
5687
5937
|
const { shutdown: gracefulShutdown } = setupGracefulShutdown({
|
|
5688
|
-
logger,
|
|
5938
|
+
logger: logger$1,
|
|
5689
5939
|
timeoutMs: 1e4,
|
|
5690
5940
|
onForceExit: () => {
|
|
5691
5941
|
for (const job of jobRunner.activeJobs.values()) job.abortController.abort();
|
|
@@ -5703,7 +5953,7 @@ await guardStartup(logger, async () => {
|
|
|
5703
5953
|
name: "Waiting for active jobs to complete",
|
|
5704
5954
|
fn: async () => {
|
|
5705
5955
|
if (jobRunner.activeJobs.size > 0) {
|
|
5706
|
-
logger.info("Active jobs remaining", { activeJobs: jobRunner.activeJobs.size });
|
|
5956
|
+
logger$1.info("Active jobs remaining", { activeJobs: jobRunner.activeJobs.size });
|
|
5707
5957
|
await Promise.allSettled([...jobRunner.activeJobs.values()].map((j) => j.completionPromise));
|
|
5708
5958
|
}
|
|
5709
5959
|
}
|
|
@@ -5737,17 +5987,17 @@ await guardStartup(logger, async () => {
|
|
|
5737
5987
|
]
|
|
5738
5988
|
});
|
|
5739
5989
|
process.on("SIGUSR1", () => {
|
|
5740
|
-
logger.info("Received SIGUSR1, entering drain mode");
|
|
5990
|
+
logger$1.info("Received SIGUSR1, entering drain mode");
|
|
5741
5991
|
isDraining = true;
|
|
5742
5992
|
if (jobRunner.activeJobs.size === 0) {
|
|
5743
|
-
logger.info("No active jobs, shutting down immediately");
|
|
5993
|
+
logger$1.info("No active jobs, shutting down immediately");
|
|
5744
5994
|
gracefulShutdown("SIGUSR1-drain");
|
|
5745
5995
|
return;
|
|
5746
5996
|
}
|
|
5747
5997
|
const checkDrained = setInterval(() => {
|
|
5748
5998
|
if (jobRunner.activeJobs.size === 0) {
|
|
5749
5999
|
clearInterval(checkDrained);
|
|
5750
|
-
logger.info("All jobs drained, shutting down");
|
|
6000
|
+
logger$1.info("All jobs drained, shutting down");
|
|
5751
6001
|
gracefulShutdown("SIGUSR1-drain");
|
|
5752
6002
|
}
|
|
5753
6003
|
}, 1e3);
|