@kici-dev/agent 0.1.23 → 0.1.25
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/execution/cache/cache-phase.d.ts +21 -4
- package/dist/execution/cache/index.d.ts +1 -1
- package/dist/execution/init-runner.d.ts +2 -1
- package/dist/execution/sandbox/capture-context.d.ts +11 -0
- package/dist/execution/sandbox/ipc-protocol.d.ts +44 -9
- package/dist/execution/sandbox/parallel-scheduler.d.ts +34 -0
- package/dist/execution/sandbox/step-loop.d.ts +91 -16
- package/dist/execution/sandbox/step-task-registry.d.ts +26 -0
- package/dist/execution/sandbox/types.d.ts +2 -2
- package/dist/execution/sandbox/workflow-runner.d.ts +22 -1
- package/dist/index.js +8 -2
- package/dist/provenance/attest.d.ts +27 -4
- package/dist/provenance/statement-builder.d.ts +50 -0
- package/dist/server.js +204 -101
- package/dist/workflow-runner.js +577 -131
- package/dist/ws/orchestrator-client.d.ts +6 -0
- package/package.json +6 -6
- package/sbom.spdx.json +68 -68
package/dist/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/s
|
|
|
15
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
|
-
import * as fs$
|
|
18
|
+
import * as fs$1 from "node:fs";
|
|
19
19
|
import fs, { existsSync } from "node:fs";
|
|
20
20
|
import { ZipArchive } from "archiver";
|
|
21
21
|
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -23,9 +23,9 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
23
23
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
24
|
import { format, promisify } from "node:util";
|
|
25
25
|
import { gcStaleTmpDirs } from "@kici-dev/core/tmp-gc";
|
|
26
|
-
import
|
|
26
|
+
import fsPromises, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
27
27
|
import Docker from "dockerode";
|
|
28
|
-
import { buildKiciApi, buildNeedsContext, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
28
|
+
import { buildKiciApi, buildNeedsContext, isDynamicGroupRef, isDynamicJobFn, isParallelGroup, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
29
29
|
import { c, x } from "tar";
|
|
30
30
|
import https from "node:https";
|
|
31
31
|
import http from "node:http";
|
|
@@ -36,7 +36,14 @@ import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, YarnFlavor, detectPackageMa
|
|
|
36
36
|
import { parse, stringify } from "yaml";
|
|
37
37
|
import { createInterface } from "node:readline";
|
|
38
38
|
var __defProp = Object.defineProperty;
|
|
39
|
-
var __esmMin = (fn, res) => () =>
|
|
39
|
+
var __esmMin = (fn, res, err) => () => {
|
|
40
|
+
if (err) throw err[0];
|
|
41
|
+
try {
|
|
42
|
+
return fn && (res = fn(fn = 0)), res;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw err = [e], e;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
40
47
|
var __exportAll = (all, no_symbols) => {
|
|
41
48
|
let target = {};
|
|
42
49
|
for (var name in all) __defProp(target, name, {
|
|
@@ -46,7 +53,6 @@ var __exportAll = (all, no_symbols) => {
|
|
|
46
53
|
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
47
54
|
return target;
|
|
48
55
|
};
|
|
49
|
-
import.meta.url;
|
|
50
56
|
//#endregion
|
|
51
57
|
//#region src/config.ts
|
|
52
58
|
/** Execution mode for the agent's sandbox backend. Mirrors the runtime enum. */
|
|
@@ -222,7 +228,7 @@ async function buildAgentMiniBundle(opts) {
|
|
|
222
228
|
uptime: os$1.uptime()
|
|
223
229
|
}, null, 2), { name: "system/info.json" });
|
|
224
230
|
if (opts.metricsText) archive.append(opts.metricsText, { name: "system/metrics.txt" });
|
|
225
|
-
if (opts.logDir && fs$
|
|
231
|
+
if (opts.logDir && fs$1.existsSync(opts.logDir)) await addLogsToArchive(archive, opts.logDir, opts.logWindowHours);
|
|
226
232
|
await archive.finalize();
|
|
227
233
|
await done;
|
|
228
234
|
return Buffer.concat(chunks);
|
|
@@ -722,6 +728,13 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
722
728
|
requestId: request.requestId
|
|
723
729
|
};
|
|
724
730
|
}
|
|
731
|
+
if (request.op === "defer") {
|
|
732
|
+
this.sendProvenanceUploadDefer(jobId, request);
|
|
733
|
+
return {
|
|
734
|
+
type: "provenance.response",
|
|
735
|
+
requestId: request.requestId
|
|
736
|
+
};
|
|
737
|
+
}
|
|
725
738
|
const uploadUrl = await this.requestProvenanceUploadUrl(jobId, request.subjectDigest);
|
|
726
739
|
return {
|
|
727
740
|
type: "provenance.response",
|
|
@@ -730,6 +743,25 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
730
743
|
};
|
|
731
744
|
}
|
|
732
745
|
/**
|
|
746
|
+
* Capture a frozen, DSSE-signed attestation for later minting (transient
|
|
747
|
+
* mint-failure path). Fire-and-forget: the orchestrator persists it into the
|
|
748
|
+
* deferred-attestation outbox and the job stays green.
|
|
749
|
+
*/
|
|
750
|
+
sendProvenanceUploadDefer(jobId, request) {
|
|
751
|
+
this.sendDirect({
|
|
752
|
+
type: "provenance.upload.defer",
|
|
753
|
+
messageId: randomUUID(),
|
|
754
|
+
jobId,
|
|
755
|
+
subjectName: request.subjectName,
|
|
756
|
+
subjectDigest: request.subjectDigest,
|
|
757
|
+
audience: request.audience,
|
|
758
|
+
mediaType: request.mediaType,
|
|
759
|
+
statementHash: request.statementHash,
|
|
760
|
+
dsseEnvelope: request.dsseEnvelope,
|
|
761
|
+
publicKey: request.publicKey
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
733
765
|
* Relay a step-level approval request to the orchestrator. Sends a
|
|
734
766
|
* `step.approval-request` WS message and resolves with the orchestrator's
|
|
735
767
|
* `step.approval-resolved` mapped onto the IPC response shape. No client-side
|
|
@@ -1310,14 +1342,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
1310
1342
|
init_console_capture();
|
|
1311
1343
|
function safe(name, fallback = "unknown") {
|
|
1312
1344
|
switch (name) {
|
|
1313
|
-
case "version": return "0.1.
|
|
1314
|
-
case "buildCommit": return "
|
|
1315
|
-
case "sdkVersion": return "0.1.
|
|
1316
|
-
case "sdkBundleHash": return "
|
|
1317
|
-
case "sharedVersion": return "0.1.
|
|
1318
|
-
case "sharedBundleHash": return "
|
|
1319
|
-
case "engineVersion": return "0.1.
|
|
1320
|
-
case "engineBundleHash": return "
|
|
1345
|
+
case "version": return "0.1.25";
|
|
1346
|
+
case "buildCommit": return "15e8e4155";
|
|
1347
|
+
case "sdkVersion": return "0.1.25";
|
|
1348
|
+
case "sdkBundleHash": return "e06bd1acf349ba365bdb357f3d865e0cbb4ad4604a0270b707a681a4f440954e";
|
|
1349
|
+
case "sharedVersion": return "0.1.25";
|
|
1350
|
+
case "sharedBundleHash": return "2394db0d8560b2cebf220c0e2d8993c75083aaf70a5917c35099b24feb3a22ba";
|
|
1351
|
+
case "engineVersion": return "0.1.25";
|
|
1352
|
+
case "engineBundleHash": return "c3320e812b8593d692f3fbf5eafe83507270028f7a1174c607881105dd702654";
|
|
1321
1353
|
default: return fallback;
|
|
1322
1354
|
}
|
|
1323
1355
|
}
|
|
@@ -1951,7 +1983,7 @@ async function buildAssetDigestFromResolvedPaths(workDir, resolvedPaths) {
|
|
|
1951
1983
|
for (const rel of resolvedPaths) {
|
|
1952
1984
|
const abs = path.join(workDir, rel);
|
|
1953
1985
|
try {
|
|
1954
|
-
const content = await
|
|
1986
|
+
const content = await fsPromises.readFile(abs, "utf-8");
|
|
1955
1987
|
parts.push(`${rel}\n${content}`);
|
|
1956
1988
|
} catch {
|
|
1957
1989
|
parts.push(`${rel}\n`);
|
|
@@ -1976,7 +2008,7 @@ async function loadWorkflowSource(workDir, sourceFile, expectedContentHash, reso
|
|
|
1976
2008
|
ensureLoaderHookRegistered();
|
|
1977
2009
|
const filePath = path.join(workDir, sourceFile);
|
|
1978
2010
|
if (expectedContentHash) {
|
|
1979
|
-
const rawSource = await
|
|
2011
|
+
const rawSource = await fsPromises.readFile(filePath, "utf-8");
|
|
1980
2012
|
let assetDigest;
|
|
1981
2013
|
if (resolvedHashFiles?.length) assetDigest = await buildAssetDigestFromResolvedPaths(workDir, resolvedHashFiles);
|
|
1982
2014
|
const actualHash = computeContentHash(rawSource, assetDigest);
|
|
@@ -2080,8 +2112,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
2080
2112
|
}
|
|
2081
2113
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
2082
2114
|
var init_workflow_loader = __esmMin((() => {
|
|
2083
|
-
AGENT_SDK_VERSION = "0.1.
|
|
2084
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
2115
|
+
AGENT_SDK_VERSION = "0.1.25";
|
|
2116
|
+
AGENT_SDK_BUNDLE_HASH = "e06bd1acf349ba365bdb357f3d865e0cbb4ad4604a0270b707a681a4f440954e";
|
|
2085
2117
|
hookRegistered = false;
|
|
2086
2118
|
}));
|
|
2087
2119
|
//#endregion
|
|
@@ -2247,24 +2279,24 @@ function resolveOrchestratorUrl(url) {
|
|
|
2247
2279
|
* have nothing to race; the defensive `rm` covers re-runs.
|
|
2248
2280
|
*/
|
|
2249
2281
|
async function moveScratchIntoRepo(scratchDir, workDir) {
|
|
2250
|
-
for (const child of await
|
|
2282
|
+
for (const child of await fsPromises.readdir(scratchDir)) if (child === ".kici") {
|
|
2251
2283
|
const kiciScratch = join(scratchDir, ".kici");
|
|
2252
|
-
for (const sub of await
|
|
2284
|
+
for (const sub of await fsPromises.readdir(kiciScratch)) await moveInto(join(kiciScratch, sub), join(workDir, ".kici", sub));
|
|
2253
2285
|
} else await moveInto(join(scratchDir, child), join(workDir, child));
|
|
2254
2286
|
}
|
|
2255
2287
|
/** Move `src` to `dest`, creating the parent and clearing any stale dest. */
|
|
2256
2288
|
async function moveInto(src, dest) {
|
|
2257
2289
|
await mkdir(dirname(dest), { recursive: true });
|
|
2258
|
-
await
|
|
2290
|
+
await fsPromises.rm(dest, {
|
|
2259
2291
|
recursive: true,
|
|
2260
2292
|
force: true
|
|
2261
2293
|
});
|
|
2262
|
-
await
|
|
2294
|
+
await fsPromises.rename(src, dest);
|
|
2263
2295
|
}
|
|
2264
2296
|
/** Best-effort cleanup of a settled scratch dir; logs and continues on failure. */
|
|
2265
2297
|
async function cleanupScratch(scratchDir) {
|
|
2266
2298
|
try {
|
|
2267
|
-
await
|
|
2299
|
+
await fsPromises.rm(scratchDir, {
|
|
2268
2300
|
recursive: true,
|
|
2269
2301
|
force: true
|
|
2270
2302
|
});
|
|
@@ -2298,7 +2330,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
2298
2330
|
const kiciDir = join(workDir, ".kici");
|
|
2299
2331
|
if (depsUrl.startsWith("file://")) {
|
|
2300
2332
|
const localPath = fileURLToPath(depsUrl);
|
|
2301
|
-
const data = await
|
|
2333
|
+
const data = await fsPromises.readFile(localPath);
|
|
2302
2334
|
if (depsHash) {
|
|
2303
2335
|
const actualHash = computeHash(data);
|
|
2304
2336
|
if (actualHash !== depsHash) throw new Error(`Dep tarball hash mismatch: expected ${depsHash}, got ${actualHash}`);
|
|
@@ -2457,7 +2489,7 @@ async function restoreSource(workDir, sourceTarUrl) {
|
|
|
2457
2489
|
let data;
|
|
2458
2490
|
if (sourceTarUrl.startsWith("file://")) {
|
|
2459
2491
|
const localPath = fileURLToPath(sourceTarUrl);
|
|
2460
|
-
data = await
|
|
2492
|
+
data = await fsPromises.readFile(localPath);
|
|
2461
2493
|
} else if (sourceTarUrl.startsWith("http://") || sourceTarUrl.startsWith("https://")) data = await downloadUrl(sourceTarUrl);
|
|
2462
2494
|
else throw new Error(`Unsupported source tarball URL scheme: ${sourceTarUrl}`);
|
|
2463
2495
|
await extractSourceTarball(data, workDir);
|
|
@@ -2548,9 +2580,16 @@ async function evaluateDynamicFields(workflow, jobName, event, flags, timeoutMs
|
|
|
2548
2580
|
if (job.include || job.exclude) combos = applyIncludeExclude(combos, job.include, job.exclude);
|
|
2549
2581
|
result.matrixValues = combos;
|
|
2550
2582
|
}
|
|
2551
|
-
if (flags.dynamicEnvironment
|
|
2552
|
-
const
|
|
2553
|
-
if (
|
|
2583
|
+
if (flags.dynamicEnvironment) {
|
|
2584
|
+
const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
|
|
2585
|
+
if (envRefs && envRefs.length > 0) {
|
|
2586
|
+
const names = [];
|
|
2587
|
+
for (const ref of envRefs) if (typeof ref === "function") {
|
|
2588
|
+
const value = await withTimeout(() => ref(event), timeoutMs, `dynamicEnvironment for job '${jobName}'`);
|
|
2589
|
+
if (value !== void 0 && value !== null) names.push(value);
|
|
2590
|
+
} else if (typeof ref === "string") names.push(ref);
|
|
2591
|
+
if (names.length > 0) result.environmentNames = names;
|
|
2592
|
+
}
|
|
2554
2593
|
}
|
|
2555
2594
|
if (flags.dynamicEnv && typeof job.env === "function") {
|
|
2556
2595
|
const value = await withTimeout(() => job.env(event), timeoutMs, `dynamicEnv for job '${jobName}'`);
|
|
@@ -2658,37 +2697,53 @@ async function sshPush(reach, privateKey, localBytes, remotePath, opts = {}, dep
|
|
|
2658
2697
|
/**
|
|
2659
2698
|
* Start a per-call ephemeral ssh-agent, load the key via stdin (never a file),
|
|
2660
2699
|
* run `body` with `SSH_AUTH_SOCK` in env, and kill the agent in `finally`.
|
|
2700
|
+
*
|
|
2701
|
+
* The agent is bound to a socket inside a private `kici-bootstrap-ssh-*`
|
|
2702
|
+
* directory (`ssh-agent -a <dir>/agent.sock`) rather than the default
|
|
2703
|
+
* `/tmp/ssh-XXXX`. `ssh-agent` daemonizes into its own session, so it does NOT
|
|
2704
|
+
* die with this process — a SIGKILL of the agent (routine when an ephemeral
|
|
2705
|
+
* bring-up runner is torn down) skips the `finally` and orphans the daemon.
|
|
2706
|
+
* The namespaced socket path is how `kici-leak-sweep` reaps such orphans
|
|
2707
|
+
* precisely: it can distinguish a KiCI bring-up agent from an operator's login
|
|
2708
|
+
* agent, which a bare `/tmp/ssh-XXXX` socket cannot. The private dir is removed
|
|
2709
|
+
* in an outer `finally` so the normal path leaves nothing behind.
|
|
2661
2710
|
*/
|
|
2662
2711
|
async function withEphemeralAgent(privateKey, spawnFn, body) {
|
|
2663
2712
|
const baseEnv = { ...process.env };
|
|
2664
|
-
const
|
|
2665
|
-
|
|
2666
|
-
const sock = parseAgentSocket(start.stdout);
|
|
2667
|
-
const pid = parseAgentPid(start.stdout);
|
|
2668
|
-
const agentEnv = {
|
|
2669
|
-
...baseEnv,
|
|
2670
|
-
SSH_AUTH_SOCK: sock,
|
|
2671
|
-
...pid ? { SSH_AGENT_PID: pid } : {},
|
|
2672
|
-
SSH_ASKPASS: "/bin/false",
|
|
2673
|
-
DISPLAY: ""
|
|
2674
|
-
};
|
|
2713
|
+
const agentDir = await mkdtemp(join(tmpdir(), "kici-bootstrap-ssh-"));
|
|
2714
|
+
const sock = join(agentDir, "agent.sock");
|
|
2675
2715
|
try {
|
|
2676
|
-
const
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2716
|
+
const start = await spawnFn("ssh-agent", [
|
|
2717
|
+
"-a",
|
|
2718
|
+
sock,
|
|
2719
|
+
"-s"
|
|
2720
|
+
], { env: baseEnv });
|
|
2721
|
+
if (start.exitCode !== 0) throw new Error(`ssh-agent start failed: exit ${start.exitCode}\n${start.stderr}`);
|
|
2722
|
+
const pid = parseAgentPid(start.stdout);
|
|
2723
|
+
const agentEnv = {
|
|
2724
|
+
...baseEnv,
|
|
2725
|
+
SSH_AUTH_SOCK: sock,
|
|
2726
|
+
...pid ? { SSH_AGENT_PID: pid } : {},
|
|
2727
|
+
SSH_ASKPASS: "/bin/false",
|
|
2728
|
+
DISPLAY: ""
|
|
2729
|
+
};
|
|
2730
|
+
try {
|
|
2731
|
+
const add = await spawnFn("ssh-add", ["-"], {
|
|
2732
|
+
env: agentEnv,
|
|
2733
|
+
stdin: privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`
|
|
2734
|
+
});
|
|
2735
|
+
if (add.exitCode !== 0) throw new Error(`ssh-add failed: exit ${add.exitCode}\n${add.stderr}`);
|
|
2736
|
+
return await body(agentEnv);
|
|
2737
|
+
} finally {
|
|
2738
|
+
await spawnFn("ssh-agent", ["-k"], { env: agentEnv }).catch(() => {});
|
|
2739
|
+
}
|
|
2682
2740
|
} finally {
|
|
2683
|
-
await
|
|
2741
|
+
await rm(agentDir, {
|
|
2742
|
+
recursive: true,
|
|
2743
|
+
force: true
|
|
2744
|
+
}).catch(() => {});
|
|
2684
2745
|
}
|
|
2685
2746
|
}
|
|
2686
|
-
/** Extract `SSH_AUTH_SOCK=<path>;` from `ssh-agent -s` output. */
|
|
2687
|
-
function parseAgentSocket(out) {
|
|
2688
|
-
const m = out.match(/SSH_AUTH_SOCK=([^;\n]+)/);
|
|
2689
|
-
if (!m) throw new Error("ssh-agent -s did not emit SSH_AUTH_SOCK");
|
|
2690
|
-
return m[1];
|
|
2691
|
-
}
|
|
2692
2747
|
/** Extract `SSH_AGENT_PID=<n>;` from `ssh-agent -s` output (best-effort). */
|
|
2693
2748
|
function parseAgentPid(out) {
|
|
2694
2749
|
return out.match(/SSH_AGENT_PID=([^;\n]+)/)?.[1];
|
|
@@ -2857,11 +2912,22 @@ async function serializeJobsToLock(jobs, ctx, staticNames, allowedGroups) {
|
|
|
2857
2912
|
}
|
|
2858
2913
|
async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups) {
|
|
2859
2914
|
const { include: runsOn, exclude: excludeLabels } = normalizeRunsOnToMatchers(job.runsOn, `generated job '${job.name}' runsOn`);
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2915
|
+
const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
|
|
2916
|
+
let resolvedEnvironments;
|
|
2917
|
+
if (envRefs !== void 0 && envRefs.length > 0) {
|
|
2918
|
+
const resolved = [];
|
|
2919
|
+
for (const ref of envRefs) if (typeof ref === "function") {
|
|
2920
|
+
const value = await withTimeout(() => ref(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic environment for generated job '${job.name}'`);
|
|
2921
|
+
if (value !== void 0 && value !== null) resolved.push({
|
|
2922
|
+
value,
|
|
2923
|
+
dynamic: false
|
|
2924
|
+
});
|
|
2925
|
+
} else if (typeof ref === "string") resolved.push({
|
|
2926
|
+
value: ref,
|
|
2927
|
+
dynamic: false
|
|
2928
|
+
});
|
|
2929
|
+
if (resolved.length > 0) resolvedEnvironments = resolved;
|
|
2930
|
+
}
|
|
2865
2931
|
let resolvedEnv;
|
|
2866
2932
|
if (typeof job.env === "function") {
|
|
2867
2933
|
const value = await withTimeout(() => job.env(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic env for generated job '${job.name}'`);
|
|
@@ -2887,7 +2953,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2887
2953
|
...job.include ? { include: job.include } : {},
|
|
2888
2954
|
...job.exclude ? { exclude: job.exclude } : {},
|
|
2889
2955
|
...job.description ? { description: job.description } : {},
|
|
2890
|
-
...
|
|
2956
|
+
...resolvedEnvironments !== void 0 ? { environments: resolvedEnvironments } : {},
|
|
2891
2957
|
...resolvedEnv !== void 0 ? { env: resolvedEnv } : {},
|
|
2892
2958
|
...resolvedConcurrencyGroup !== void 0 ? { concurrencyGroup: resolvedConcurrencyGroup } : {}
|
|
2893
2959
|
};
|
|
@@ -2903,7 +2969,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2903
2969
|
*/
|
|
2904
2970
|
function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
2905
2971
|
if (!needs || needs.length === 0) return [];
|
|
2906
|
-
const allNames = new Set([...generatedNames, ...staticNames]);
|
|
2972
|
+
const allNames = /* @__PURE__ */ new Set([...generatedNames, ...staticNames]);
|
|
2907
2973
|
return needs.map((dep) => {
|
|
2908
2974
|
if (typeof dep === "string") {
|
|
2909
2975
|
if (!allNames.has(dep)) throw new Error(`Job dependency '${dep}' not found in workflow jobs (checked: ${generatedNames.size} generated, ${staticNames.size} static)`);
|
|
@@ -2945,26 +3011,41 @@ function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
|
2945
3011
|
* are loaded from the workflow bundle at execution time.
|
|
2946
3012
|
*/
|
|
2947
3013
|
function serializeSteps(steps) {
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
delayMs: step.retry.delayMs,
|
|
2962
|
-
backoff: step.retry.backoff,
|
|
2963
|
-
maxDelayMs: step.retry.maxDelayMs
|
|
2964
|
-
} } : {}
|
|
2965
|
-
};
|
|
3014
|
+
let flatIndex = 0;
|
|
3015
|
+
return steps.map((entry) => {
|
|
3016
|
+
if (isParallelGroup(entry)) {
|
|
3017
|
+
const children = entry.steps.map((child) => serializeSequentialStep(child, flatIndex++));
|
|
3018
|
+
return {
|
|
3019
|
+
kind: "parallel",
|
|
3020
|
+
name: entry.name ?? `parallel-${children[0]?.name ?? "group"}`,
|
|
3021
|
+
failFast: entry.failFast,
|
|
3022
|
+
...entry.maxParallel !== void 0 ? { maxParallel: entry.maxParallel } : {},
|
|
3023
|
+
children
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
return serializeSequentialStep(entry, flatIndex++);
|
|
2966
3027
|
});
|
|
2967
3028
|
}
|
|
3029
|
+
/** Serialize one sequential step (or bare function) to a flat `LockStep`. */
|
|
3030
|
+
function serializeSequentialStep(stepOrFn, index) {
|
|
3031
|
+
if (typeof stepOrFn === "function") return {
|
|
3032
|
+
name: `step-${index}`,
|
|
3033
|
+
hasOutputs: false
|
|
3034
|
+
};
|
|
3035
|
+
const step = stepOrFn;
|
|
3036
|
+
return {
|
|
3037
|
+
name: step.name || `step-${index}`,
|
|
3038
|
+
hasOutputs: !!step.outputs,
|
|
3039
|
+
...step.continueOnError ? { continueOnError: true } : {},
|
|
3040
|
+
...step.timeout ? { timeout: step.timeout } : {},
|
|
3041
|
+
...step.retry ? { retry: {
|
|
3042
|
+
maxAttempts: step.retry.maxAttempts,
|
|
3043
|
+
delayMs: step.retry.delayMs,
|
|
3044
|
+
backoff: step.retry.backoff,
|
|
3045
|
+
maxDelayMs: step.retry.maxDelayMs
|
|
3046
|
+
} } : {}
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
2968
3049
|
/**
|
|
2969
3050
|
* Serialize matrix configuration. Static array/object matrices are embedded as-is;
|
|
2970
3051
|
* dynamic matrix functions are invoked against the eval context (mirroring the
|
|
@@ -3285,7 +3366,7 @@ function decryptBuffer(encrypted, aesKey) {
|
|
|
3285
3366
|
*/
|
|
3286
3367
|
async function applyOverlay(config) {
|
|
3287
3368
|
const { tarballUrl, cliPublicKey, orchestratorPrivateKey, repoDir } = config;
|
|
3288
|
-
const tmpDir = await
|
|
3369
|
+
const tmpDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
|
|
3289
3370
|
try {
|
|
3290
3371
|
logger$7.info("Downloading overlay tarball", { url: tarballUrl.replace(/\?.*$/, "?[redacted]") });
|
|
3291
3372
|
let encryptedData;
|
|
@@ -3299,7 +3380,7 @@ async function applyOverlay(config) {
|
|
|
3299
3380
|
const decryptedData = decryptBuffer(encryptedData, aesKey);
|
|
3300
3381
|
logger$7.info("Extracting overlay tarball", { size: decryptedData.length });
|
|
3301
3382
|
const extractDir = path.join(tmpDir, "extracted");
|
|
3302
|
-
await
|
|
3383
|
+
await fsPromises.mkdir(extractDir, { recursive: true });
|
|
3303
3384
|
try {
|
|
3304
3385
|
const readable = Readable.from(decryptedData);
|
|
3305
3386
|
await new Promise((resolve, reject) => {
|
|
@@ -3314,7 +3395,7 @@ async function applyOverlay(config) {
|
|
|
3314
3395
|
const manifestPath = path.join(extractDir, ".kici-overlay-tmp", "manifest.json");
|
|
3315
3396
|
let manifestContent;
|
|
3316
3397
|
try {
|
|
3317
|
-
manifestContent = await
|
|
3398
|
+
manifestContent = await fsPromises.readFile(manifestPath, "utf-8");
|
|
3318
3399
|
} catch {
|
|
3319
3400
|
throw new Error("Overlay manifest not found: expected .kici-overlay-tmp/manifest.json in tarball");
|
|
3320
3401
|
}
|
|
@@ -3335,15 +3416,15 @@ async function applyOverlay(config) {
|
|
|
3335
3416
|
for (const file of checksumFiles) {
|
|
3336
3417
|
const srcPath = path.join(extractDir, file);
|
|
3337
3418
|
const destPath = path.join(repoDir, file);
|
|
3338
|
-
await
|
|
3339
|
-
await
|
|
3419
|
+
await fsPromises.mkdir(path.dirname(destPath), { recursive: true });
|
|
3420
|
+
await fsPromises.copyFile(srcPath, destPath);
|
|
3340
3421
|
filesApplied++;
|
|
3341
3422
|
}
|
|
3342
3423
|
let filesDeleted = 0;
|
|
3343
3424
|
for (const file of manifest.deletions) {
|
|
3344
3425
|
const targetPath = path.join(repoDir, file);
|
|
3345
3426
|
try {
|
|
3346
|
-
await
|
|
3427
|
+
await fsPromises.unlink(targetPath);
|
|
3347
3428
|
filesDeleted++;
|
|
3348
3429
|
} catch {
|
|
3349
3430
|
logger$7.debug("Deletion target not found, skipping", { file });
|
|
@@ -3359,7 +3440,7 @@ async function applyOverlay(config) {
|
|
|
3359
3440
|
verified: true
|
|
3360
3441
|
};
|
|
3361
3442
|
} finally {
|
|
3362
|
-
await
|
|
3443
|
+
await fsPromises.rm(tmpDir, {
|
|
3363
3444
|
recursive: true,
|
|
3364
3445
|
force: true
|
|
3365
3446
|
}).catch(() => {});
|
|
@@ -4883,10 +4964,17 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
4883
4964
|
case "log.line":
|
|
4884
4965
|
ctx.execOptions.onLogLine(msg.stepIndex, msg.line);
|
|
4885
4966
|
return;
|
|
4886
|
-
case "step.start":
|
|
4967
|
+
case "step.start": {
|
|
4887
4968
|
ctx.stepNames.set(msg.stepIndex, msg.stepName);
|
|
4888
|
-
|
|
4969
|
+
const startState = msg.state === "pending" ? ExecutionStepStatus.enum.pending : ExecutionStepStatus.enum.running;
|
|
4970
|
+
const startData = {
|
|
4971
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
4972
|
+
...msg.groupId && { groupId: msg.groupId }
|
|
4973
|
+
};
|
|
4974
|
+
if (Object.keys(startData).length > 0) ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, startState, startData);
|
|
4975
|
+
else ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, startState);
|
|
4889
4976
|
return;
|
|
4977
|
+
}
|
|
4890
4978
|
case "step.complete":
|
|
4891
4979
|
ctx.execOptions.onStepStatus(msg.stepIndex, ctx.stepNames.get(msg.stepIndex) ?? "", msg.status, {
|
|
4892
4980
|
durationMs: msg.durationMs,
|
|
@@ -4896,6 +4984,8 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
4896
4984
|
...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
|
|
4897
4985
|
...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
|
|
4898
4986
|
...msg.drift !== void 0 && { drift: msg.drift },
|
|
4987
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
4988
|
+
...msg.groupId && { groupId: msg.groupId },
|
|
4899
4989
|
...msg.data && msg.data
|
|
4900
4990
|
});
|
|
4901
4991
|
return;
|
|
@@ -5609,10 +5699,17 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
5609
5699
|
case "ready":
|
|
5610
5700
|
this.sendExecuteRequest(stream, options);
|
|
5611
5701
|
return false;
|
|
5612
|
-
case "step.start":
|
|
5702
|
+
case "step.start": {
|
|
5613
5703
|
stepNames.set(msg.stepIndex, msg.stepName);
|
|
5614
|
-
|
|
5704
|
+
const startState = msg.state === "pending" ? ExecutionStepStatus.enum.pending : ExecutionStepStatus.enum.running;
|
|
5705
|
+
const startData = {
|
|
5706
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
5707
|
+
...msg.groupId && { groupId: msg.groupId }
|
|
5708
|
+
};
|
|
5709
|
+
if (Object.keys(startData).length > 0) options.onStepStatus(msg.stepIndex, msg.stepName, startState, startData);
|
|
5710
|
+
else options.onStepStatus(msg.stepIndex, msg.stepName, startState);
|
|
5615
5711
|
return false;
|
|
5712
|
+
}
|
|
5616
5713
|
case "step.complete": {
|
|
5617
5714
|
const name = stepNames.get(msg.stepIndex) ?? `step-${msg.stepIndex}`;
|
|
5618
5715
|
options.onStepStatus(msg.stepIndex, name, msg.status, {
|
|
@@ -5623,6 +5720,8 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
5623
5720
|
...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
|
|
5624
5721
|
...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
|
|
5625
5722
|
...msg.drift !== void 0 && { drift: msg.drift },
|
|
5723
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
5724
|
+
...msg.groupId && { groupId: msg.groupId },
|
|
5626
5725
|
...msg.data && msg.data
|
|
5627
5726
|
});
|
|
5628
5727
|
stepResults.push({
|
|
@@ -5763,7 +5862,7 @@ var job_runner_exports = /* @__PURE__ */ __exportAll({
|
|
|
5763
5862
|
*/
|
|
5764
5863
|
async function fileExists(p) {
|
|
5765
5864
|
try {
|
|
5766
|
-
await
|
|
5865
|
+
await fsPromises.access(p);
|
|
5767
5866
|
return true;
|
|
5768
5867
|
} catch {
|
|
5769
5868
|
return false;
|
|
@@ -5879,11 +5978,11 @@ var init_job_runner = __esmMin((() => {
|
|
|
5879
5978
|
async execute(dispatch) {
|
|
5880
5979
|
const { runId: _runId, jobId, jobConfig: _jobConfig } = dispatch;
|
|
5881
5980
|
const abortController = new AbortController();
|
|
5882
|
-
const workDir = await
|
|
5981
|
+
const workDir = await fsPromises.mkdtemp(join(tmpdir(), "kici-"));
|
|
5883
5982
|
const completionPromise = this.runJob(dispatch, workDir, abortController).finally(async () => {
|
|
5884
5983
|
this.activeJobs.delete(jobId);
|
|
5885
5984
|
this.activeSandbox = null;
|
|
5886
|
-
await
|
|
5985
|
+
await fsPromises.rm(workDir, {
|
|
5887
5986
|
recursive: true,
|
|
5888
5987
|
force: true
|
|
5889
5988
|
}).catch(() => {});
|
|
@@ -6494,7 +6593,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
6494
6593
|
});
|
|
6495
6594
|
logger$2.info("Init job completed successfully", {
|
|
6496
6595
|
jobId,
|
|
6497
|
-
hasEnvironment: initResult.
|
|
6596
|
+
hasEnvironment: initResult.environmentNames !== void 0,
|
|
6498
6597
|
hasEnv: initResult.env !== void 0,
|
|
6499
6598
|
hasConcurrencyGroup: initResult.concurrencyGroup !== void 0
|
|
6500
6599
|
});
|
|
@@ -6801,7 +6900,9 @@ var init_job_runner = __esmMin((() => {
|
|
|
6801
6900
|
*/
|
|
6802
6901
|
sendStepStatus(dispatch, stepIndex, stepName, state, data, logBytesStreamed) {
|
|
6803
6902
|
const secretsAccessed = data?.secretsAccessed;
|
|
6804
|
-
const
|
|
6903
|
+
const concurrencyKind = data?.concurrencyKind;
|
|
6904
|
+
const groupId = data?.groupId;
|
|
6905
|
+
const { secretsAccessed: _s, concurrencyKind: _c, groupId: _g, ...restData } = data ?? {};
|
|
6805
6906
|
const hasRestData = Object.keys(restData).length > 0;
|
|
6806
6907
|
this.sendDirect({
|
|
6807
6908
|
type: "step.status",
|
|
@@ -6814,6 +6915,8 @@ var init_job_runner = __esmMin((() => {
|
|
|
6814
6915
|
timestamp: Date.now(),
|
|
6815
6916
|
...hasRestData && { data: restData },
|
|
6816
6917
|
...secretsAccessed !== void 0 && { secretsAccessed },
|
|
6918
|
+
...concurrencyKind !== void 0 && { concurrencyKind },
|
|
6919
|
+
...groupId !== void 0 && { groupId },
|
|
6817
6920
|
...logBytesStreamed !== void 0 && { logBytesStreamed }
|
|
6818
6921
|
});
|
|
6819
6922
|
}
|
|
@@ -6841,14 +6944,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
6841
6944
|
*/
|
|
6842
6945
|
init_console_capture();
|
|
6843
6946
|
init_npm_resolver();
|
|
6844
|
-
const AGENT_VERSION = "0.1.
|
|
6845
|
-
const BUILD_COMMIT = "
|
|
6846
|
-
const SDK_VERSION = "0.1.
|
|
6847
|
-
const SDK_BUNDLE_HASH = "
|
|
6848
|
-
const SHARED_VERSION = "0.1.
|
|
6849
|
-
const SHARED_BUNDLE_HASH = "
|
|
6850
|
-
const ENGINE_VERSION = "0.1.
|
|
6851
|
-
const ENGINE_BUNDLE_HASH = "
|
|
6947
|
+
const AGENT_VERSION = "0.1.25";
|
|
6948
|
+
const BUILD_COMMIT = "15e8e4155";
|
|
6949
|
+
const SDK_VERSION = "0.1.25";
|
|
6950
|
+
const SDK_BUNDLE_HASH = "e06bd1acf349ba365bdb357f3d865e0cbb4ad4604a0270b707a681a4f440954e";
|
|
6951
|
+
const SHARED_VERSION = "0.1.25";
|
|
6952
|
+
const SHARED_BUNDLE_HASH = "2394db0d8560b2cebf220c0e2d8993c75083aaf70a5917c35099b24feb3a22ba";
|
|
6953
|
+
const ENGINE_VERSION = "0.1.25";
|
|
6954
|
+
const ENGINE_BUNDLE_HASH = "c3320e812b8593d692f3fbf5eafe83507270028f7a1174c607881105dd702654";
|
|
6852
6955
|
initTelemetry({
|
|
6853
6956
|
serviceName: "kici-agent",
|
|
6854
6957
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|