@kici-dev/agent 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +6 -0
- package/dist/execution/job-runner.d.ts +10 -0
- package/dist/execution/reboot.d.ts +26 -0
- package/dist/execution/sandbox/ipc-protocol.d.ts +30 -3
- package/dist/execution/sandbox/step-loop.d.ts +33 -5
- package/dist/execution/sandbox/workflow-runner.d.ts +11 -0
- package/dist/index.js +7 -2
- package/dist/server.js +180 -59
- package/dist/workflow-runner.js +224 -47
- package/dist/ws/orchestrator-client.d.ts +7 -0
- package/package.json +5 -5
- package/sbom.spdx.json +71 -122
package/dist/server.js
CHANGED
|
@@ -12,7 +12,7 @@ import winston from "winston";
|
|
|
12
12
|
import { RingBuffer, addLogsToArchive, chunkBuffer, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, deriveSharedSecret, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, logger, normalizeLineEndings, redactConfig, requestContext, setServiceName, setupGracefulShutdown, sha256, sha256File, toErrorMessage, validateRequiredTools } from "@kici-dev/shared";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
|
|
15
|
-
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, applyIncludeExclude, deriveOsArchLabels, expandMatrix, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, resolveRoleLabels, validateNoReservedLabels } from "@kici-dev/engine";
|
|
15
|
+
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, applyIncludeExclude, deriveOsArchLabels, expandMatrix, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, parseHostPropertyAssignments, resolveRoleLabels, resolveWhenToRunOn, validateNoReservedLabels } from "@kici-dev/engine";
|
|
16
16
|
import { execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
|
|
17
17
|
import WebSocket from "ws";
|
|
18
18
|
import * as fs$2 from "node:fs";
|
|
@@ -66,6 +66,7 @@ const envDef = defineEnv({
|
|
|
66
66
|
orchestratorUrl: z.string().url().min(1, "KICI_ORCHESTRATOR_URL is required"),
|
|
67
67
|
agentId: z.string().optional(),
|
|
68
68
|
labels: z.string().default("").transform((s) => s.split(",").filter(Boolean)),
|
|
69
|
+
properties: z.string().default("").transform((s) => parseHostPropertyAssignments(s.split(",").filter(Boolean))),
|
|
69
70
|
roles: z.string().optional().transform((s) => {
|
|
70
71
|
if (s === void 0) return void 0;
|
|
71
72
|
if (s === "") return [];
|
|
@@ -101,16 +102,19 @@ const envDef = defineEnv({
|
|
|
101
102
|
scalerPendingDispatchTimeoutMs: z.coerce.number().default(6e4),
|
|
102
103
|
executionMode: ExecutionMode.optional(),
|
|
103
104
|
otelExporterOtlpEndpoint: z.string().optional(),
|
|
104
|
-
concurrencyWaitTimeoutMs: z.coerce.number().int().min(1e3).default(36e5)
|
|
105
|
+
concurrencyWaitTimeoutMs: z.coerce.number().int().min(1e3).default(36e5),
|
|
106
|
+
isOrchestratorHost: z.string().optional().transform((v) => v === "true")
|
|
105
107
|
}),
|
|
106
108
|
envMap: {
|
|
107
109
|
orchestratorUrl: "KICI_ORCHESTRATOR_URL",
|
|
108
110
|
agentId: "KICI_AGENT_ID",
|
|
109
111
|
labels: "KICI_LABELS",
|
|
112
|
+
properties: "KICI_PROPERTIES",
|
|
110
113
|
roles: "KICI_ROLES",
|
|
111
114
|
port: "KICI_PORT",
|
|
112
115
|
logLevel: "KICI_LOG_LEVEL",
|
|
113
116
|
agentToken: "KICI_AGENT_TOKEN",
|
|
117
|
+
isOrchestratorHost: "KICI_AGENT_IS_ORCHESTRATOR_HOST",
|
|
114
118
|
githubToken: "KICI_GITHUB_TOKEN",
|
|
115
119
|
maxLogSizeBytes: "KICI_MAX_LOG_SIZE_BYTES",
|
|
116
120
|
defaultStepTimeoutMs: "KICI_DEFAULT_STEP_TIMEOUT_MS",
|
|
@@ -134,6 +138,7 @@ const envDef = defineEnv({
|
|
|
134
138
|
* - KICI_ORCHESTRATOR_URL (required)
|
|
135
139
|
* - KICI_AGENT_ID (optional, auto-generated from hostname-uuid8)
|
|
136
140
|
* - KICI_LABELS (comma-separated, e.g. "linux,docker"). Labels with 'kici-' prefix are reserved.
|
|
141
|
+
* - KICI_PROPERTIES (comma-separated key=value host-vars, e.g. "region=eu,cores=8,gpu=true"). Typed (bool/number/string), reported into the host roster.
|
|
137
142
|
* - KICI_ROLES (comma-separated agent roles, e.g. "builder,init-runner". undefined=all, empty=execution-only)
|
|
138
143
|
* - KICI_PORT (default: 8080)
|
|
139
144
|
* - KICI_LOG_LEVEL (default: info)
|
|
@@ -177,6 +182,7 @@ function agentClientConnectionOptions(config) {
|
|
|
177
182
|
url: config.orchestratorUrl,
|
|
178
183
|
agentId: config.agentId,
|
|
179
184
|
labels: config.labels,
|
|
185
|
+
properties: config.properties,
|
|
180
186
|
scalerManaged: config.scalerManaged,
|
|
181
187
|
token: config.agentToken
|
|
182
188
|
};
|
|
@@ -282,7 +288,7 @@ var LogBuffer = class extends RingBuffer {
|
|
|
282
288
|
};
|
|
283
289
|
//#endregion
|
|
284
290
|
//#region src/ws/orchestrator-client.ts
|
|
285
|
-
const logger$
|
|
291
|
+
const logger$12 = createLogger({ prefix: "orchestrator-client" });
|
|
286
292
|
/**
|
|
287
293
|
* WebSocket client that connects the agent to the customer orchestrator.
|
|
288
294
|
*
|
|
@@ -329,6 +335,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
329
335
|
url;
|
|
330
336
|
agentId;
|
|
331
337
|
labels;
|
|
338
|
+
properties;
|
|
332
339
|
onJobDispatch;
|
|
333
340
|
onJobCancel;
|
|
334
341
|
token;
|
|
@@ -356,6 +363,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
356
363
|
this.url = options.url;
|
|
357
364
|
this.agentId = options.agentId;
|
|
358
365
|
this.labels = options.labels;
|
|
366
|
+
this.properties = options.properties ?? {};
|
|
359
367
|
this.onJobDispatch = options.onJobDispatch;
|
|
360
368
|
this.onJobCancel = options.onJobCancel;
|
|
361
369
|
this.token = options.token;
|
|
@@ -397,7 +405,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
397
405
|
*/
|
|
398
406
|
connect() {
|
|
399
407
|
if (this._state !== "disconnected") {
|
|
400
|
-
logger$
|
|
408
|
+
logger$12.warn("connect() called while not disconnected", { state: this._state });
|
|
401
409
|
return;
|
|
402
410
|
}
|
|
403
411
|
this.intentionalDisconnect = false;
|
|
@@ -748,7 +756,8 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
748
756
|
stepName: request.stepName,
|
|
749
757
|
clauses: request.clauses,
|
|
750
758
|
reason: request.reason,
|
|
751
|
-
...request.timeoutSeconds !== void 0 && { timeoutSeconds: request.timeoutSeconds }
|
|
759
|
+
...request.timeoutSeconds !== void 0 && { timeoutSeconds: request.timeoutSeconds },
|
|
760
|
+
...request.payload !== void 0 && { payload: request.payload }
|
|
752
761
|
});
|
|
753
762
|
});
|
|
754
763
|
}
|
|
@@ -834,7 +843,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
834
843
|
}
|
|
835
844
|
});
|
|
836
845
|
} catch (err) {
|
|
837
|
-
logger$
|
|
846
|
+
logger$12.error("Failed to create WebSocket", { error: toErrorMessage(err) });
|
|
838
847
|
this._state = "disconnected";
|
|
839
848
|
this.scheduleReconnect();
|
|
840
849
|
return;
|
|
@@ -842,7 +851,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
842
851
|
this.ws.on("open", () => {
|
|
843
852
|
if (this.token) {
|
|
844
853
|
this._state = "authenticating";
|
|
845
|
-
logger$
|
|
854
|
+
logger$12.info("Connected to orchestrator, sending auth.request", {
|
|
846
855
|
url: this.url,
|
|
847
856
|
agentId: this.agentId
|
|
848
857
|
});
|
|
@@ -853,7 +862,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
853
862
|
}));
|
|
854
863
|
} else {
|
|
855
864
|
this._state = "registering";
|
|
856
|
-
logger$
|
|
865
|
+
logger$12.info("Connected to orchestrator, sending agent.register (no token)", {
|
|
857
866
|
url: this.url,
|
|
858
867
|
agentId: this.agentId
|
|
859
868
|
});
|
|
@@ -864,12 +873,12 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
864
873
|
this.handleMessage(data);
|
|
865
874
|
});
|
|
866
875
|
this.ws.on("close", (code, reason) => {
|
|
867
|
-
logger$
|
|
876
|
+
logger$12.info("Orchestrator connection closed", {
|
|
868
877
|
code,
|
|
869
878
|
reason: reason.toString()
|
|
870
879
|
});
|
|
871
880
|
if (code === WS_CLOSE_AGENT_AUTH_FAILED) {
|
|
872
|
-
logger$
|
|
881
|
+
logger$12.error("Orchestrator closed with auth-failed code -- token is invalid or revoked. NOT retrying.", {
|
|
873
882
|
code,
|
|
874
883
|
reason: reason.toString()
|
|
875
884
|
});
|
|
@@ -895,7 +904,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
895
904
|
if (!this.intentionalDisconnect) this.scheduleReconnect();
|
|
896
905
|
});
|
|
897
906
|
this.ws.on("error", (err) => {
|
|
898
|
-
logger$
|
|
907
|
+
logger$12.error(`Orchestrator WebSocket error: ${err.message}`);
|
|
899
908
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.close();
|
|
900
909
|
});
|
|
901
910
|
}
|
|
@@ -904,7 +913,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
904
913
|
try {
|
|
905
914
|
raw = JSON.parse(data.toString());
|
|
906
915
|
} catch {
|
|
907
|
-
logger$
|
|
916
|
+
logger$12.warn("Malformed JSON received from orchestrator");
|
|
908
917
|
return;
|
|
909
918
|
}
|
|
910
919
|
const rawMsg = raw;
|
|
@@ -962,13 +971,13 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
962
971
|
switch (msg.type) {
|
|
963
972
|
case "auth.success":
|
|
964
973
|
if (this._state === "authenticating") {
|
|
965
|
-
logger$
|
|
974
|
+
logger$12.info("Authentication successful, sending agent.register", { connectionId: msg.connectionId });
|
|
966
975
|
this._state = "registering";
|
|
967
976
|
this.sendAgentRegister();
|
|
968
977
|
}
|
|
969
978
|
break;
|
|
970
979
|
case "auth.failure":
|
|
971
|
-
logger$
|
|
980
|
+
logger$12.error("Authentication FAILED -- token is invalid or expired. NOT retrying.", { reason: msg.reason });
|
|
972
981
|
this.authFailed = true;
|
|
973
982
|
this.intentionalDisconnect = true;
|
|
974
983
|
if (this.ws) {
|
|
@@ -978,7 +987,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
978
987
|
this._state = "disconnected";
|
|
979
988
|
break;
|
|
980
989
|
case "register.ack":
|
|
981
|
-
logger$
|
|
990
|
+
logger$12.info("Registration acknowledged by orchestrator", {
|
|
982
991
|
agentId: msg.agentId,
|
|
983
992
|
labels: msg.labels,
|
|
984
993
|
scalerManaged: msg.scalerManaged,
|
|
@@ -993,14 +1002,14 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
993
1002
|
this.sendConfigAck(msg.agentId);
|
|
994
1003
|
break;
|
|
995
1004
|
case "job.dispatch":
|
|
996
|
-
logger$
|
|
1005
|
+
logger$12.info("Job dispatch received", {
|
|
997
1006
|
runId: msg.runId,
|
|
998
1007
|
jobId: msg.jobId
|
|
999
1008
|
});
|
|
1000
1009
|
this.onJobDispatch(msg);
|
|
1001
1010
|
break;
|
|
1002
1011
|
case "job.cancel":
|
|
1003
|
-
logger$
|
|
1012
|
+
logger$12.info("Job cancel received", {
|
|
1004
1013
|
runId: msg.runId,
|
|
1005
1014
|
jobId: msg.jobId,
|
|
1006
1015
|
reason: msg.reason
|
|
@@ -1008,7 +1017,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1008
1017
|
this.onJobCancel(msg);
|
|
1009
1018
|
break;
|
|
1010
1019
|
case "job.concurrency.ack": {
|
|
1011
|
-
logger$
|
|
1020
|
+
logger$12.info("Concurrency ack received", {
|
|
1012
1021
|
requestId: msg.requestId,
|
|
1013
1022
|
action: msg.action
|
|
1014
1023
|
});
|
|
@@ -1023,7 +1032,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1023
1032
|
break;
|
|
1024
1033
|
}
|
|
1025
1034
|
case "step.approval-resolved": {
|
|
1026
|
-
logger$
|
|
1035
|
+
logger$12.info("Step approval resolved", {
|
|
1027
1036
|
requestId: msg.requestId,
|
|
1028
1037
|
runId: msg.runId,
|
|
1029
1038
|
jobId: msg.jobId,
|
|
@@ -1043,7 +1052,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1043
1052
|
break;
|
|
1044
1053
|
}
|
|
1045
1054
|
case "fleet.logs.request":
|
|
1046
|
-
logger$
|
|
1055
|
+
logger$12.info("Fleet log collection requested", {
|
|
1047
1056
|
requestId: msg.requestId,
|
|
1048
1057
|
logWindowHours: msg.logWindowHours
|
|
1049
1058
|
});
|
|
@@ -1053,7 +1062,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1053
1062
|
return;
|
|
1054
1063
|
}
|
|
1055
1064
|
if (heartbeatSchema.safeParse(raw).success) return;
|
|
1056
|
-
logger$
|
|
1065
|
+
logger$12.warn("Invalid message from orchestrator", { errors: parsed.error.issues });
|
|
1057
1066
|
}
|
|
1058
1067
|
flushBuffer() {
|
|
1059
1068
|
const events = this.eventBuffer.flush();
|
|
@@ -1067,11 +1076,11 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1067
1076
|
}
|
|
1068
1077
|
this.disconnectedAt = null;
|
|
1069
1078
|
if (events.length > 0) {
|
|
1070
|
-
logger$
|
|
1079
|
+
logger$12.info("Flushing event buffer", { count: events.length });
|
|
1071
1080
|
for (const msg of events) this.sendDirect(msg);
|
|
1072
1081
|
}
|
|
1073
1082
|
if (logLines.length > 0) {
|
|
1074
|
-
logger$
|
|
1083
|
+
logger$12.info("Flushing log buffer", { count: logLines.length });
|
|
1075
1084
|
for (let i = 0; i < logLines.length; i += OrchestratorClient.LOG_BATCH_SIZE) {
|
|
1076
1085
|
const batch = logLines.slice(i, i + OrchestratorClient.LOG_BATCH_SIZE);
|
|
1077
1086
|
this.sendAgentLogMessage(batch);
|
|
@@ -1124,14 +1133,14 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1124
1133
|
*/
|
|
1125
1134
|
blockMmdsAccess() {
|
|
1126
1135
|
if (process.getuid?.() !== 0) {
|
|
1127
|
-
logger$
|
|
1136
|
+
logger$12.info("MMDS iptables block skipped (non-root) — network isolation handled by orchestrator");
|
|
1128
1137
|
return;
|
|
1129
1138
|
}
|
|
1130
1139
|
try {
|
|
1131
1140
|
execSync("iptables -A OUTPUT -d 169.254.169.254 -j DROP", { timeout: 5e3 });
|
|
1132
|
-
logger$
|
|
1141
|
+
logger$12.info("MMDS access blocked via iptables");
|
|
1133
1142
|
} catch (err) {
|
|
1134
|
-
logger$
|
|
1143
|
+
logger$12.warn("Failed to block MMDS access via iptables", { error: toErrorMessage(err) });
|
|
1135
1144
|
}
|
|
1136
1145
|
}
|
|
1137
1146
|
/**
|
|
@@ -1145,7 +1154,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1145
1154
|
messageId: `config-ack-${agentId}-${Date.now()}`,
|
|
1146
1155
|
agentId
|
|
1147
1156
|
}));
|
|
1148
|
-
logger$
|
|
1157
|
+
logger$12.info("Config ACK sent to orchestrator", { agentId });
|
|
1149
1158
|
}
|
|
1150
1159
|
}
|
|
1151
1160
|
startHeartbeat() {
|
|
@@ -1205,17 +1214,18 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1205
1214
|
})()
|
|
1206
1215
|
};
|
|
1207
1216
|
if (inFlightJobs.length > 0) msg.inFlightJobs = inFlightJobs;
|
|
1217
|
+
if (Object.keys(this.properties).length > 0) msg.properties = this.properties;
|
|
1208
1218
|
this.ws.send(JSON.stringify(msg));
|
|
1209
1219
|
}
|
|
1210
1220
|
scheduleReconnect() {
|
|
1211
1221
|
this.cancelReconnect();
|
|
1212
1222
|
if (this.authFailed) {
|
|
1213
|
-
logger$
|
|
1223
|
+
logger$12.error("Not reconnecting: authentication permanently failed");
|
|
1214
1224
|
return;
|
|
1215
1225
|
}
|
|
1216
1226
|
const delay = this.getReconnectDelay();
|
|
1217
1227
|
this.reconnectAttempts++;
|
|
1218
|
-
logger$
|
|
1228
|
+
logger$12.info("Scheduling reconnect", {
|
|
1219
1229
|
attempt: this.reconnectAttempts,
|
|
1220
1230
|
delayMs: Math.round(delay)
|
|
1221
1231
|
});
|
|
@@ -1300,14 +1310,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
1300
1310
|
init_console_capture();
|
|
1301
1311
|
function safe(name, fallback = "unknown") {
|
|
1302
1312
|
switch (name) {
|
|
1303
|
-
case "version": return "0.1.
|
|
1304
|
-
case "buildCommit": return "
|
|
1305
|
-
case "sdkVersion": return "0.1.
|
|
1306
|
-
case "sdkBundleHash": return "
|
|
1307
|
-
case "sharedVersion": return "0.1.
|
|
1308
|
-
case "sharedBundleHash": return "
|
|
1309
|
-
case "engineVersion": return "0.1.
|
|
1310
|
-
case "engineBundleHash": return "
|
|
1313
|
+
case "version": return "0.1.22";
|
|
1314
|
+
case "buildCommit": return "5afd16303";
|
|
1315
|
+
case "sdkVersion": return "0.1.22";
|
|
1316
|
+
case "sdkBundleHash": return "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
|
|
1317
|
+
case "sharedVersion": return "0.1.22";
|
|
1318
|
+
case "sharedBundleHash": return "991385c024392c395d3eb8a68946ef8ed3fcba96f2652f21c3164a54eafa1b1b";
|
|
1319
|
+
case "engineVersion": return "0.1.22";
|
|
1320
|
+
case "engineBundleHash": return "ff74a3afb2b1db2870c03d47543642f64d04b660bebdcde6681f76d874f26757";
|
|
1311
1321
|
default: return fallback;
|
|
1312
1322
|
}
|
|
1313
1323
|
}
|
|
@@ -1564,6 +1574,83 @@ async function gcStaleAgentTmpDirs(base = tmpdir()) {
|
|
|
1564
1574
|
})];
|
|
1565
1575
|
}
|
|
1566
1576
|
//#endregion
|
|
1577
|
+
//#region src/execution/reboot.ts
|
|
1578
|
+
/**
|
|
1579
|
+
* Cross-OS host reboot for the workflow-level `restartHost()` step.
|
|
1580
|
+
*
|
|
1581
|
+
* The agent runs ON the host it executes jobs for, so a `restartHost()` step
|
|
1582
|
+
* reboots that host. `rebootCommandFor` is the pure OS→command mapping (kept
|
|
1583
|
+
* pure for unit-testing); `issueReboot` spawns it detached with a tiny grace so
|
|
1584
|
+
* the job's final flush completes before the box goes down.
|
|
1585
|
+
*
|
|
1586
|
+
* Rebooting needs host privilege (root/admin). If the primitive is denied, the
|
|
1587
|
+
* spawn fails and the caller clears the orchestrator's reboot-pending flag and
|
|
1588
|
+
* surfaces the error — the deadline sweep is the backstop.
|
|
1589
|
+
*/
|
|
1590
|
+
const logger$11 = createLogger({ prefix: "reboot" });
|
|
1591
|
+
/** The OS reboot primitive for a Node platform string. */
|
|
1592
|
+
function rebootCommandFor(platform) {
|
|
1593
|
+
switch (platform) {
|
|
1594
|
+
case "darwin": return {
|
|
1595
|
+
cmd: "shutdown",
|
|
1596
|
+
args: ["-r", "now"]
|
|
1597
|
+
};
|
|
1598
|
+
case "win32": return {
|
|
1599
|
+
cmd: "shutdown",
|
|
1600
|
+
args: [
|
|
1601
|
+
"/r",
|
|
1602
|
+
"/t",
|
|
1603
|
+
"0"
|
|
1604
|
+
]
|
|
1605
|
+
};
|
|
1606
|
+
default: return {
|
|
1607
|
+
cmd: "systemctl",
|
|
1608
|
+
args: ["reboot"]
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Issue the OS reboot detached. Resolves once the child has been spawned (the
|
|
1614
|
+
* box is going down; there is nothing to await). Rejects synchronously if the
|
|
1615
|
+
* spawn itself fails (e.g. the binary is missing). A privilege denial usually
|
|
1616
|
+
* surfaces as a non-zero exit AFTER spawn — logged, not thrown, because by then
|
|
1617
|
+
* the box may already be on its way down.
|
|
1618
|
+
*/
|
|
1619
|
+
function issueReboot(platform = process.platform) {
|
|
1620
|
+
const { cmd, args } = rebootCommandFor(platform);
|
|
1621
|
+
return new Promise((resolve, reject) => {
|
|
1622
|
+
try {
|
|
1623
|
+
const child = spawn(cmd, args, {
|
|
1624
|
+
detached: true,
|
|
1625
|
+
stdio: "ignore"
|
|
1626
|
+
});
|
|
1627
|
+
child.on("error", (err) => {
|
|
1628
|
+
logger$11.error("Reboot command failed to spawn", {
|
|
1629
|
+
cmd,
|
|
1630
|
+
args,
|
|
1631
|
+
error: String(err)
|
|
1632
|
+
});
|
|
1633
|
+
reject(err);
|
|
1634
|
+
});
|
|
1635
|
+
child.on("exit", (code) => {
|
|
1636
|
+
if (code !== 0 && code !== null) logger$11.warn("Reboot command exited non-zero (privilege denied?)", {
|
|
1637
|
+
cmd,
|
|
1638
|
+
args,
|
|
1639
|
+
code
|
|
1640
|
+
});
|
|
1641
|
+
});
|
|
1642
|
+
child.unref();
|
|
1643
|
+
logger$11.info("Issued host reboot", {
|
|
1644
|
+
cmd,
|
|
1645
|
+
args
|
|
1646
|
+
});
|
|
1647
|
+
setImmediate(resolve);
|
|
1648
|
+
} catch (err) {
|
|
1649
|
+
reject(err);
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
//#endregion
|
|
1567
1654
|
//#region src/metrics/prometheus.ts
|
|
1568
1655
|
var prometheus_exports = /* @__PURE__ */ __exportAll({
|
|
1569
1656
|
cloneDurationSeconds: () => cloneDurationSeconds,
|
|
@@ -1993,8 +2080,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
1993
2080
|
}
|
|
1994
2081
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
1995
2082
|
var init_workflow_loader = __esmMin((() => {
|
|
1996
|
-
AGENT_SDK_VERSION = "0.1.
|
|
1997
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
2083
|
+
AGENT_SDK_VERSION = "0.1.22";
|
|
2084
|
+
AGENT_SDK_BUNDLE_HASH = "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
|
|
1998
2085
|
hookRegistered = false;
|
|
1999
2086
|
}));
|
|
2000
2087
|
//#endregion
|
|
@@ -2543,7 +2630,8 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2543
2630
|
* Validates against generatedNames union staticNames union allowedGroups.
|
|
2544
2631
|
*
|
|
2545
2632
|
* Returns the lock file representation: strings for concrete refs,
|
|
2546
|
-
* NeedsEntry for { name,
|
|
2633
|
+
* NeedsEntry for { name, when }, NeedsGroupEntry for group refs (each `when`
|
|
2634
|
+
* normalized to a runOn status-set).
|
|
2547
2635
|
*/
|
|
2548
2636
|
function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
2549
2637
|
if (!needs || needs.length === 0) return [];
|
|
@@ -2558,7 +2646,7 @@ function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
|
2558
2646
|
if (!allowedGroups.has(groupRef.group)) throw new Error(`Dynamic group '${groupRef.group}' not found in workflow (available groups: ${[...allowedGroups].join(", ") || "none"})`);
|
|
2559
2647
|
return {
|
|
2560
2648
|
group: groupRef.group,
|
|
2561
|
-
|
|
2649
|
+
runOn: resolveWhenToRunOn(groupRef.when)
|
|
2562
2650
|
};
|
|
2563
2651
|
}
|
|
2564
2652
|
if (typeof dep === "object" && dep !== null && "group" in dep) {
|
|
@@ -2566,7 +2654,7 @@ function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
|
2566
2654
|
if (!allowedGroups.has(groupDep.group)) throw new Error(`Dynamic group '${groupDep.group}' not found in workflow (available groups: ${[...allowedGroups].join(", ") || "none"})`);
|
|
2567
2655
|
return {
|
|
2568
2656
|
group: groupDep.group,
|
|
2569
|
-
|
|
2657
|
+
runOn: resolveWhenToRunOn(groupDep.when)
|
|
2570
2658
|
};
|
|
2571
2659
|
}
|
|
2572
2660
|
if (typeof dep === "object" && dep !== null && "name" in dep && !("steps" in dep)) {
|
|
@@ -2574,7 +2662,7 @@ function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
|
2574
2662
|
if (!allNames.has(namedDep.name)) throw new Error(`Job dependency '${namedDep.name}' not found in workflow jobs (checked: ${generatedNames.size} generated, ${staticNames.size} static)`);
|
|
2575
2663
|
return {
|
|
2576
2664
|
name: namedDep.name,
|
|
2577
|
-
|
|
2665
|
+
runOn: resolveWhenToRunOn(namedDep.when)
|
|
2578
2666
|
};
|
|
2579
2667
|
}
|
|
2580
2668
|
if (isDynamicJobFn(dep)) throw new Error("Job dependency cannot be a DynamicJobFn");
|
|
@@ -4188,6 +4276,7 @@ function buildRequest(dispatch, workDir) {
|
|
|
4188
4276
|
checkout: jobConfig.checkout ?? true,
|
|
4189
4277
|
isTestRun: jobConfig.isTestRun ?? false,
|
|
4190
4278
|
fullRepo: jobConfig.fullRepo ?? false,
|
|
4279
|
+
checkMode: jobConfig.checkMode,
|
|
4191
4280
|
tarballUrl: jobConfig.tarballUrl,
|
|
4192
4281
|
cliPublicKey: jobConfig.cliPublicKey,
|
|
4193
4282
|
orchestratorPrivateKey: jobConfig.orchestratorPrivateKey,
|
|
@@ -4204,6 +4293,8 @@ function buildRequest(dispatch, workDir) {
|
|
|
4204
4293
|
concurrencyEvaluationTimeoutMs: jobConfig.concurrencyEvaluationTimeoutMs,
|
|
4205
4294
|
branch: dispatch.ref,
|
|
4206
4295
|
upstreamJobOutputs: dispatch.upstreamJobOutputs,
|
|
4296
|
+
upstreamJobStatuses: dispatch.upstreamJobStatuses,
|
|
4297
|
+
jobNeeds: jobConfig.needs,
|
|
4207
4298
|
npmRegistries: dispatch.npmRegistries,
|
|
4208
4299
|
installEnvSecrets: dispatch.installEnvSecrets,
|
|
4209
4300
|
jobIdShort: dispatch.jobId.slice(0, 8),
|
|
@@ -4525,6 +4616,9 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
4525
4616
|
...msg.error && { error: msg.error },
|
|
4526
4617
|
...msg.secretsAccessed && { secretsAccessed: msg.secretsAccessed },
|
|
4527
4618
|
...msg.step_type && { step_type: msg.step_type },
|
|
4619
|
+
...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
|
|
4620
|
+
...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
|
|
4621
|
+
...msg.drift !== void 0 && { drift: msg.drift },
|
|
4528
4622
|
...msg.data && msg.data
|
|
4529
4623
|
});
|
|
4530
4624
|
return;
|
|
@@ -5249,6 +5343,9 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
5249
5343
|
...msg.error && { error: msg.error },
|
|
5250
5344
|
...msg.secretsAccessed && { secretsAccessed: msg.secretsAccessed },
|
|
5251
5345
|
...msg.step_type && { step_type: msg.step_type },
|
|
5346
|
+
...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
|
|
5347
|
+
...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
|
|
5348
|
+
...msg.drift !== void 0 && { drift: msg.drift },
|
|
5252
5349
|
...msg.data && msg.data
|
|
5253
5350
|
});
|
|
5254
5351
|
stepResults.push({
|
|
@@ -5380,7 +5477,10 @@ var init_sandbox = __esmMin((() => {
|
|
|
5380
5477
|
}));
|
|
5381
5478
|
//#endregion
|
|
5382
5479
|
//#region src/execution/job-runner.ts
|
|
5383
|
-
var job_runner_exports = /* @__PURE__ */ __exportAll({
|
|
5480
|
+
var job_runner_exports = /* @__PURE__ */ __exportAll({
|
|
5481
|
+
JobRunner: () => JobRunner$1,
|
|
5482
|
+
buildEvalNeedsContext: () => buildEvalNeedsContext
|
|
5483
|
+
});
|
|
5384
5484
|
/**
|
|
5385
5485
|
* Check if a file exists at the given path.
|
|
5386
5486
|
*/
|
|
@@ -5764,12 +5864,17 @@ var init_job_runner = __esmMin((() => {
|
|
|
5764
5864
|
stepsTotal.add(1, { status: stepResult.status });
|
|
5765
5865
|
if (stepResult.durationMs > 0) stepDurationSeconds.record(stepResult.durationMs / 1e3);
|
|
5766
5866
|
}
|
|
5767
|
-
if (result.status === ExecutionJobStatus.enum.failed)
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5867
|
+
if (result.status === ExecutionJobStatus.enum.failed) {
|
|
5868
|
+
const stepErrors = result.stepResults.filter((r) => r.error).map((r) => `${r.name}: ${r.error.message}`).join(" | ");
|
|
5869
|
+
logger$2.error("Sandbox returned failed result", {
|
|
5870
|
+
durationMs: result.durationMs,
|
|
5871
|
+
stepCount: result.stepResults.length,
|
|
5872
|
+
steps: result.stepResults.map((r) => `${r.name}:${r.status}`).join(","),
|
|
5873
|
+
logStreamerKeys: [...logStreamers.keys()].join(","),
|
|
5874
|
+
...result.error && { error: result.error },
|
|
5875
|
+
...stepErrors && { stepErrors }
|
|
5876
|
+
});
|
|
5877
|
+
}
|
|
5773
5878
|
this.sendJobStatus(dispatch, result.status, {
|
|
5774
5879
|
durationMs: result.durationMs,
|
|
5775
5880
|
...result.error && { error: result.error },
|
|
@@ -6406,14 +6511,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
6406
6511
|
*/
|
|
6407
6512
|
init_console_capture();
|
|
6408
6513
|
init_npm_resolver();
|
|
6409
|
-
const AGENT_VERSION = "0.1.
|
|
6410
|
-
const BUILD_COMMIT = "
|
|
6411
|
-
const SDK_VERSION = "0.1.
|
|
6412
|
-
const SDK_BUNDLE_HASH = "
|
|
6413
|
-
const SHARED_VERSION = "0.1.
|
|
6414
|
-
const SHARED_BUNDLE_HASH = "
|
|
6415
|
-
const ENGINE_VERSION = "0.1.
|
|
6416
|
-
const ENGINE_BUNDLE_HASH = "
|
|
6514
|
+
const AGENT_VERSION = "0.1.22";
|
|
6515
|
+
const BUILD_COMMIT = "5afd16303";
|
|
6516
|
+
const SDK_VERSION = "0.1.22";
|
|
6517
|
+
const SDK_BUNDLE_HASH = "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
|
|
6518
|
+
const SHARED_VERSION = "0.1.22";
|
|
6519
|
+
const SHARED_BUNDLE_HASH = "991385c024392c395d3eb8a68946ef8ed3fcba96f2652f21c3164a54eafa1b1b";
|
|
6520
|
+
const ENGINE_VERSION = "0.1.22";
|
|
6521
|
+
const ENGINE_BUNDLE_HASH = "ff74a3afb2b1db2870c03d47543642f64d04b660bebdcde6681f76d874f26757";
|
|
6417
6522
|
initTelemetry({
|
|
6418
6523
|
serviceName: "kici-agent",
|
|
6419
6524
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
@@ -6479,6 +6584,7 @@ await guardStartup(logger$1, async () => {
|
|
|
6479
6584
|
}
|
|
6480
6585
|
let isDraining = false;
|
|
6481
6586
|
let idleShutdownTimer;
|
|
6587
|
+
let rebootIntent = false;
|
|
6482
6588
|
let client;
|
|
6483
6589
|
const jobRunner = new JobRunner({
|
|
6484
6590
|
send: (msg) => client.send(msg),
|
|
@@ -6492,7 +6598,15 @@ await guardStartup(logger$1, async () => {
|
|
|
6492
6598
|
sendJobContext: (runId, jobId, context) => client.sendJobContext(runId, jobId, context),
|
|
6493
6599
|
sendRunEvent: (runId, eventType, opts) => client.sendRunEvent(runId, eventType, opts),
|
|
6494
6600
|
sendConcurrencyReport: (runId, jobId, group) => client.sendConcurrencyReport(runId, jobId, group),
|
|
6495
|
-
sendApiRequest: (method, params) =>
|
|
6601
|
+
sendApiRequest: async (method, params) => {
|
|
6602
|
+
if (method === "host.requestReboot") {
|
|
6603
|
+
if (config.isOrchestratorHost) throw new Error("refusing to reboot the orchestrator host");
|
|
6604
|
+
const result = await client.sendApiRequest(method, params ?? {});
|
|
6605
|
+
rebootIntent = true;
|
|
6606
|
+
return result;
|
|
6607
|
+
}
|
|
6608
|
+
return client.sendApiRequest(method, params ?? {});
|
|
6609
|
+
},
|
|
6496
6610
|
requestUserCache: (jobId, request) => client.requestUserCache(jobId, request),
|
|
6497
6611
|
relayProvenance: (jobId, request) => client.relayProvenance(jobId, request),
|
|
6498
6612
|
sendStepApproval: (runId, jobId, request) => client.sendStepApproval(runId, jobId, request)
|
|
@@ -6581,6 +6695,13 @@ await guardStartup(logger$1, async () => {
|
|
|
6581
6695
|
jobsActive.add(-1);
|
|
6582
6696
|
metricsReporter.collectAndSend().catch(() => {});
|
|
6583
6697
|
sendAgentStatus();
|
|
6698
|
+
if (rebootIntent) {
|
|
6699
|
+
rebootIntent = false;
|
|
6700
|
+
issueReboot().catch((err) => {
|
|
6701
|
+
logger$1.error("Host reboot failed; cancelling reboot-pending flag", { error: toErrorMessage(err) });
|
|
6702
|
+
client.sendApiRequest("host.cancelReboot", {}).catch(() => {});
|
|
6703
|
+
});
|
|
6704
|
+
}
|
|
6584
6705
|
if (config.scalerManaged && jobRunner.activeJobs.size === 0) {
|
|
6585
6706
|
if (client.state !== "registered") {
|
|
6586
6707
|
logger$1.info("Scaler-managed agent idle but disconnected, deferring shutdown until reconnected");
|