@kici-dev/agent 0.1.16 → 0.1.18
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/dep-installer.d.ts +12 -3
- package/dist/execution/dep-packer.d.ts +9 -1
- package/dist/execution/env-init/presets/directives.d.ts +20 -0
- package/dist/execution/env-init/presets/expand.d.ts +16 -0
- package/dist/execution/env-init/presets/mise/cache-key.d.ts +7 -0
- package/dist/execution/env-init/presets/mise/expander.d.ts +16 -0
- package/dist/execution/env-init/presets/mise/templates.d.ts +16 -0
- package/dist/execution/env-init/presets/mise/windows-install.d.ts +18 -0
- package/dist/execution/env-init/presets/registry.d.ts +31 -0
- package/dist/execution/init-runner.d.ts +7 -0
- package/dist/execution/job-runner.d.ts +9 -1
- package/dist/execution/sandbox/env-delta.d.ts +2 -0
- package/dist/execution/sandbox/index.d.ts +1 -1
- package/dist/execution/sandbox/ipc-protocol.d.ts +81 -3
- package/dist/execution/sandbox/types.d.ts +9 -1
- package/dist/execution/sandbox/workflow-runner.d.ts +12 -7
- package/dist/execution/validate-kici-deps.d.ts +10 -2
- package/dist/execution/workflow-loader.d.ts +5 -1
- package/dist/execution/workspace-siblings.d.ts +33 -0
- package/dist/execution/yarnrc-berry-config.d.ts +23 -0
- package/dist/index.js +423 -17
- package/dist/provenance/attest.d.ts +30 -0
- package/dist/provenance/sign.d.ts +21 -0
- package/dist/provenance/statement-builder.d.ts +38 -0
- package/dist/server.js +703 -160
- package/dist/version.d.ts +2 -0
- package/dist/workflow-runner.js +955 -59
- package/dist/ws/orchestrator-client.d.ts +16 -1
- package/package.json +14 -12
- package/sbom.spdx.json +2526 -5994
package/dist/server.js
CHANGED
|
@@ -12,25 +12,27 @@ 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, deriveOsArchLabels, 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, resolveRoleLabels, 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
|
|
18
|
+
import { ZipArchive } from "archiver";
|
|
19
|
+
import fs, { existsSync } from "node:fs";
|
|
20
|
+
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
22
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
23
|
import { format, promisify } from "node:util";
|
|
21
|
-
import { existsSync } from "node:fs";
|
|
22
|
-
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
23
24
|
import { gcStaleTmpDirs } from "@kici-dev/core/tmp-gc";
|
|
24
|
-
import {
|
|
25
|
-
import fs, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
25
|
+
import fs$1, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
26
26
|
import Docker from "dockerode";
|
|
27
|
-
import { buildKiciApi, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
27
|
+
import { buildKiciApi, buildNeedsContext, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
28
28
|
import { c, x } from "tar";
|
|
29
29
|
import https from "node:https";
|
|
30
30
|
import http from "node:http";
|
|
31
31
|
import { pipeline } from "node:stream/promises";
|
|
32
32
|
import { createGunzip } from "node:zlib";
|
|
33
|
-
import {
|
|
33
|
+
import { normalizeRunsOnToMatchers } from "@kici-dev/engine/labels/compile";
|
|
34
|
+
import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, YarnFlavor, detectPackageManagerFromManifests, detectYarnFlavor } from "@kici-dev/shared/package-manager";
|
|
35
|
+
import { parse, stringify } from "yaml";
|
|
34
36
|
import { createInterface } from "node:readline";
|
|
35
37
|
var __defProp = Object.defineProperty;
|
|
36
38
|
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -189,7 +191,7 @@ function agentClientConnectionOptions(config) {
|
|
|
189
191
|
* lean subset of the orchestrator's createDebugBundle.
|
|
190
192
|
*/
|
|
191
193
|
async function buildAgentMiniBundle(opts) {
|
|
192
|
-
const archive =
|
|
194
|
+
const archive = new ZipArchive({ zlib: { level: 6 } });
|
|
193
195
|
const chunks = [];
|
|
194
196
|
archive.on("data", (d) => chunks.push(d));
|
|
195
197
|
const done = new Promise((resolve, reject) => {
|
|
@@ -219,6 +221,35 @@ async function buildAgentMiniBundle(opts) {
|
|
|
219
221
|
return Buffer.concat(chunks);
|
|
220
222
|
}
|
|
221
223
|
//#endregion
|
|
224
|
+
//#region src/version.ts
|
|
225
|
+
/**
|
|
226
|
+
* Read the agent's own package version from disk.
|
|
227
|
+
*
|
|
228
|
+
* Walks up from this module's location until it finds a package.json whose
|
|
229
|
+
* name is '@kici-dev/agent'. This is robust to the agent's bundled dist layout
|
|
230
|
+
* (build-service.mjs emits dist/index.js / dist/server.js, so a fixed relative
|
|
231
|
+
* depth would be wrong). Returns null when it can't be resolved so the
|
|
232
|
+
* agent.register message simply omits the field rather than failing.
|
|
233
|
+
*/
|
|
234
|
+
function readAgentVersion() {
|
|
235
|
+
try {
|
|
236
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
237
|
+
for (let i = 0; i < 8; i++) {
|
|
238
|
+
const candidate = path.join(dir, "package.json");
|
|
239
|
+
if (fs.existsSync(candidate)) {
|
|
240
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
241
|
+
if (pkg.name === "@kici-dev/agent" && typeof pkg.version === "string") return pkg.version;
|
|
242
|
+
}
|
|
243
|
+
const parent = path.dirname(dir);
|
|
244
|
+
if (parent === dir) break;
|
|
245
|
+
dir = parent;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
222
253
|
//#region src/ws/event-buffer.ts
|
|
223
254
|
/**
|
|
224
255
|
* In-memory buffer for agent-to-orchestrator messages during disconnection.
|
|
@@ -467,6 +498,47 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
467
498
|
});
|
|
468
499
|
}
|
|
469
500
|
/**
|
|
501
|
+
* Request a presigned PUT URL for a provenance bundle. Sends a
|
|
502
|
+
* `provenance.upload.request` and waits for a `provenance.upload.response`
|
|
503
|
+
* (resolved via the shared upload-request pending map). Times out after 30s.
|
|
504
|
+
*/
|
|
505
|
+
async requestProvenanceUploadUrl(jobId, subjectDigest) {
|
|
506
|
+
const messageId = randomUUID();
|
|
507
|
+
return new Promise((resolve, reject) => {
|
|
508
|
+
const timer = setTimeout(() => {
|
|
509
|
+
this.pendingUploadRequests.delete(messageId);
|
|
510
|
+
reject(/* @__PURE__ */ new Error("Provenance upload URL request timed out (30s)"));
|
|
511
|
+
}, 3e4);
|
|
512
|
+
this.pendingUploadRequests.set(messageId, {
|
|
513
|
+
resolve: (url) => {
|
|
514
|
+
clearTimeout(timer);
|
|
515
|
+
resolve(url);
|
|
516
|
+
},
|
|
517
|
+
reject: (err) => {
|
|
518
|
+
clearTimeout(timer);
|
|
519
|
+
reject(err);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
this.sendDirect({
|
|
523
|
+
type: "provenance.upload.request",
|
|
524
|
+
messageId,
|
|
525
|
+
jobId,
|
|
526
|
+
subjectDigest
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/** Notify the orchestrator a provenance bundle upload completed (records an attestations row). */
|
|
531
|
+
sendProvenanceUploadComplete(jobId, subjectName, subjectDigest, mediaType) {
|
|
532
|
+
this.sendDirect({
|
|
533
|
+
type: "provenance.upload.complete",
|
|
534
|
+
messageId: randomUUID(),
|
|
535
|
+
jobId,
|
|
536
|
+
subjectName,
|
|
537
|
+
subjectDigest,
|
|
538
|
+
mediaType
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
470
542
|
* Send an event.emit WS message to the orchestrator and await the response.
|
|
471
543
|
*
|
|
472
544
|
* Used by the job runner to relay custom event emissions from the sandbox
|
|
@@ -628,6 +700,27 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
628
700
|
});
|
|
629
701
|
}
|
|
630
702
|
/**
|
|
703
|
+
* Relay a provenance bundle upload operation to the orchestrator. Maps the
|
|
704
|
+
* IPC `provenance.request` onto `requestProvenanceUploadUrl` (returns the
|
|
705
|
+
* presigned URL) or `sendProvenanceUploadComplete` (fire-and-forget) and
|
|
706
|
+
* returns the result on the IPC response shape.
|
|
707
|
+
*/
|
|
708
|
+
async relayProvenance(jobId, request) {
|
|
709
|
+
if (request.op === "complete") {
|
|
710
|
+
this.sendProvenanceUploadComplete(jobId, request.subjectName, request.subjectDigest, request.mediaType);
|
|
711
|
+
return {
|
|
712
|
+
type: "provenance.response",
|
|
713
|
+
requestId: request.requestId
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const uploadUrl = await this.requestProvenanceUploadUrl(jobId, request.subjectDigest);
|
|
717
|
+
return {
|
|
718
|
+
type: "provenance.response",
|
|
719
|
+
requestId: request.requestId,
|
|
720
|
+
uploadUrl
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
631
724
|
* Relay a step-level approval request to the orchestrator. Sends a
|
|
632
725
|
* `step.approval-request` WS message and resolves with the orchestrator's
|
|
633
726
|
* `step.approval-resolved` mapped onto the IPC response shape. No client-side
|
|
@@ -814,7 +907,7 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
814
907
|
return;
|
|
815
908
|
}
|
|
816
909
|
const rawMsg = raw;
|
|
817
|
-
if (rawMsg.type === "cache.upload.response") {
|
|
910
|
+
if (rawMsg.type === "cache.upload.response" || rawMsg.type === "provenance.upload.response") {
|
|
818
911
|
const pending = this.pendingUploadRequests.get(rawMsg.requestId);
|
|
819
912
|
if (pending) {
|
|
820
913
|
this.pendingUploadRequests.delete(rawMsg.requestId);
|
|
@@ -1094,6 +1187,10 @@ var OrchestratorClient = class OrchestratorClient {
|
|
|
1094
1187
|
totalMemoryMb: Math.round(os.totalmem() / (1024 * 1024)),
|
|
1095
1188
|
cpuCount: os.cpus().length,
|
|
1096
1189
|
nodeVersion: process.versions.node,
|
|
1190
|
+
...(() => {
|
|
1191
|
+
const v = readAgentVersion();
|
|
1192
|
+
return v ? { version: v } : {};
|
|
1193
|
+
})(),
|
|
1097
1194
|
...(() => {
|
|
1098
1195
|
try {
|
|
1099
1196
|
const info = os.userInfo();
|
|
@@ -1202,14 +1299,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
1202
1299
|
init_console_capture();
|
|
1203
1300
|
function safe(name, fallback = "unknown") {
|
|
1204
1301
|
switch (name) {
|
|
1205
|
-
case "version": return "0.1.
|
|
1206
|
-
case "buildCommit": return "
|
|
1207
|
-
case "sdkVersion": return "0.1.
|
|
1208
|
-
case "sdkBundleHash": return "
|
|
1209
|
-
case "sharedVersion": return "0.1.
|
|
1210
|
-
case "sharedBundleHash": return "
|
|
1211
|
-
case "engineVersion": return "0.1.
|
|
1212
|
-
case "engineBundleHash": return "
|
|
1302
|
+
case "version": return "0.1.18";
|
|
1303
|
+
case "buildCommit": return "d8cff38bb";
|
|
1304
|
+
case "sdkVersion": return "0.1.18";
|
|
1305
|
+
case "sdkBundleHash": return "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
1306
|
+
case "sharedVersion": return "0.1.18";
|
|
1307
|
+
case "sharedBundleHash": return "5f2c220f24d166b0f13d0620a2e80d19689ac8b683a12154daef44e938fd46a7";
|
|
1308
|
+
case "engineVersion": return "0.1.18";
|
|
1309
|
+
case "engineBundleHash": return "79ce14640d1798eaaa7cb3aa6c4bc325da3bff1e9f4716936e19614eabb1d858";
|
|
1213
1310
|
default: return fallback;
|
|
1214
1311
|
}
|
|
1215
1312
|
}
|
|
@@ -1766,7 +1863,7 @@ async function buildAssetDigestFromResolvedPaths(workDir, resolvedPaths) {
|
|
|
1766
1863
|
for (const rel of resolvedPaths) {
|
|
1767
1864
|
const abs = path.join(workDir, rel);
|
|
1768
1865
|
try {
|
|
1769
|
-
const content = await fs.readFile(abs, "utf-8");
|
|
1866
|
+
const content = await fs$1.readFile(abs, "utf-8");
|
|
1770
1867
|
parts.push(`${rel}\n${content}`);
|
|
1771
1868
|
} catch {
|
|
1772
1869
|
parts.push(`${rel}\n`);
|
|
@@ -1791,7 +1888,7 @@ async function loadWorkflowSource(workDir, sourceFile, expectedContentHash, reso
|
|
|
1791
1888
|
ensureLoaderHookRegistered();
|
|
1792
1889
|
const filePath = path.join(workDir, sourceFile);
|
|
1793
1890
|
if (expectedContentHash) {
|
|
1794
|
-
const rawSource = await fs.readFile(filePath, "utf-8");
|
|
1891
|
+
const rawSource = await fs$1.readFile(filePath, "utf-8");
|
|
1795
1892
|
let assetDigest;
|
|
1796
1893
|
if (resolvedHashFiles?.length) assetDigest = await buildAssetDigestFromResolvedPaths(workDir, resolvedHashFiles);
|
|
1797
1894
|
const actualHash = computeContentHash(rawSource, assetDigest);
|
|
@@ -1854,18 +1951,20 @@ function extractSteps(workflow, jobName) {
|
|
|
1854
1951
|
* A sibling mismatch logs a warning; a missing target job throws a clear
|
|
1855
1952
|
* determinism error.
|
|
1856
1953
|
*/
|
|
1857
|
-
async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event, env, apiTransport, expectedJobNames) {
|
|
1954
|
+
async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event, env, apiTransport, expectedJobNames, upstreamSnapshot, declaredNeeds) {
|
|
1858
1955
|
const dynamicFn = extractDynamicJobFn(workflow, dynamicIndex);
|
|
1859
1956
|
const { $ } = await import("zx");
|
|
1860
1957
|
const { createLogger } = await import("@kici-dev/shared");
|
|
1861
|
-
const { buildKiciApi } = await import("@kici-dev/sdk");
|
|
1958
|
+
const { buildKiciApi, buildNeedsContext } = await import("@kici-dev/sdk");
|
|
1862
1959
|
const log = createLogger({ prefix: `dynamic-job-fn:${workflow.name}` });
|
|
1863
1960
|
const kici = buildKiciApi(apiTransport ?? (() => Promise.reject(/* @__PURE__ */ new Error("Agent API not available during re-evaluation"))));
|
|
1961
|
+
const needs = upstreamSnapshot ? buildNeedsContext(upstreamSnapshot, declaredNeeds ?? []) : void 0;
|
|
1864
1962
|
const generatedJobs = await dynamicFn({
|
|
1865
1963
|
$,
|
|
1866
1964
|
ctx: {
|
|
1867
1965
|
workflow: { name: workflow.name },
|
|
1868
|
-
event
|
|
1966
|
+
event,
|
|
1967
|
+
...needs && { needs }
|
|
1869
1968
|
},
|
|
1870
1969
|
log,
|
|
1871
1970
|
env,
|
|
@@ -1893,8 +1992,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
1893
1992
|
}
|
|
1894
1993
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
1895
1994
|
var init_workflow_loader = __esmMin((() => {
|
|
1896
|
-
AGENT_SDK_VERSION = "0.1.
|
|
1897
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
1995
|
+
AGENT_SDK_VERSION = "0.1.18";
|
|
1996
|
+
AGENT_SDK_BUNDLE_HASH = "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
1898
1997
|
hookRegistered = false;
|
|
1899
1998
|
}));
|
|
1900
1999
|
//#endregion
|
|
@@ -2060,24 +2159,24 @@ function resolveOrchestratorUrl(url) {
|
|
|
2060
2159
|
* have nothing to race; the defensive `rm` covers re-runs.
|
|
2061
2160
|
*/
|
|
2062
2161
|
async function moveScratchIntoRepo(scratchDir, workDir) {
|
|
2063
|
-
for (const child of await fs.readdir(scratchDir)) if (child === ".kici") {
|
|
2162
|
+
for (const child of await fs$1.readdir(scratchDir)) if (child === ".kici") {
|
|
2064
2163
|
const kiciScratch = join(scratchDir, ".kici");
|
|
2065
|
-
for (const sub of await fs.readdir(kiciScratch)) await moveInto(join(kiciScratch, sub), join(workDir, ".kici", sub));
|
|
2164
|
+
for (const sub of await fs$1.readdir(kiciScratch)) await moveInto(join(kiciScratch, sub), join(workDir, ".kici", sub));
|
|
2066
2165
|
} else await moveInto(join(scratchDir, child), join(workDir, child));
|
|
2067
2166
|
}
|
|
2068
2167
|
/** Move `src` to `dest`, creating the parent and clearing any stale dest. */
|
|
2069
2168
|
async function moveInto(src, dest) {
|
|
2070
2169
|
await mkdir(dirname(dest), { recursive: true });
|
|
2071
|
-
await fs.rm(dest, {
|
|
2170
|
+
await fs$1.rm(dest, {
|
|
2072
2171
|
recursive: true,
|
|
2073
2172
|
force: true
|
|
2074
2173
|
});
|
|
2075
|
-
await fs.rename(src, dest);
|
|
2174
|
+
await fs$1.rename(src, dest);
|
|
2076
2175
|
}
|
|
2077
2176
|
/** Best-effort cleanup of a settled scratch dir; logs and continues on failure. */
|
|
2078
2177
|
async function cleanupScratch(scratchDir) {
|
|
2079
2178
|
try {
|
|
2080
|
-
await fs.rm(scratchDir, {
|
|
2179
|
+
await fs$1.rm(scratchDir, {
|
|
2081
2180
|
recursive: true,
|
|
2082
2181
|
force: true
|
|
2083
2182
|
});
|
|
@@ -2111,7 +2210,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
|
|
|
2111
2210
|
const kiciDir = join(workDir, ".kici");
|
|
2112
2211
|
if (depsUrl.startsWith("file://")) {
|
|
2113
2212
|
const localPath = fileURLToPath(depsUrl);
|
|
2114
|
-
const data = await fs.readFile(localPath);
|
|
2213
|
+
const data = await fs$1.readFile(localPath);
|
|
2115
2214
|
if (depsHash) {
|
|
2116
2215
|
const actualHash = computeHash(data);
|
|
2117
2216
|
if (actualHash !== depsHash) throw new Error(`Dep tarball hash mismatch: expected ${depsHash}, got ${actualHash}`);
|
|
@@ -2270,7 +2369,7 @@ async function restoreSource(workDir, sourceTarUrl) {
|
|
|
2270
2369
|
let data;
|
|
2271
2370
|
if (sourceTarUrl.startsWith("file://")) {
|
|
2272
2371
|
const localPath = fileURLToPath(sourceTarUrl);
|
|
2273
|
-
data = await fs.readFile(localPath);
|
|
2372
|
+
data = await fs$1.readFile(localPath);
|
|
2274
2373
|
} else if (sourceTarUrl.startsWith("http://") || sourceTarUrl.startsWith("https://")) data = await downloadUrl(sourceTarUrl);
|
|
2275
2374
|
else throw new Error(`Unsupported source tarball URL scheme: ${sourceTarUrl}`);
|
|
2276
2375
|
await extractSourceTarball(data, workDir);
|
|
@@ -2339,6 +2438,28 @@ function findJobByName(workflow, jobName) {
|
|
|
2339
2438
|
async function evaluateDynamicFields(workflow, jobName, event, flags, timeoutMs = 6e4) {
|
|
2340
2439
|
const job = findJobByName(workflow, jobName);
|
|
2341
2440
|
const result = {};
|
|
2441
|
+
if (flags.dynamicMatrix && typeof job.matrix === "function") {
|
|
2442
|
+
const matrixContext = {
|
|
2443
|
+
$: (await import("zx")).$,
|
|
2444
|
+
ctx: {
|
|
2445
|
+
workflow: { name: workflow.name },
|
|
2446
|
+
job: {
|
|
2447
|
+
name: jobName,
|
|
2448
|
+
runsOn: job.runsOn
|
|
2449
|
+
}
|
|
2450
|
+
},
|
|
2451
|
+
log: {
|
|
2452
|
+
info: () => {},
|
|
2453
|
+
warn: () => {},
|
|
2454
|
+
error: () => {},
|
|
2455
|
+
debug: () => {}
|
|
2456
|
+
},
|
|
2457
|
+
env: { ...process.env }
|
|
2458
|
+
};
|
|
2459
|
+
let combos = expandMatrix(await withTimeout(() => job.matrix(matrixContext), timeoutMs, `dynamicMatrix for job '${jobName}'`));
|
|
2460
|
+
if (job.include || job.exclude) combos = applyIncludeExclude(combos, job.include, job.exclude);
|
|
2461
|
+
result.matrixValues = combos;
|
|
2462
|
+
}
|
|
2342
2463
|
if (flags.dynamicEnvironment && typeof job.environment === "function") {
|
|
2343
2464
|
const value = await withTimeout(() => job.environment(event), timeoutMs, `dynamicEnvironment for job '${jobName}'`);
|
|
2344
2465
|
if (value !== void 0 && value !== null) result.environmentName = value;
|
|
@@ -2379,7 +2500,7 @@ async function serializeJobsToLock(jobs, ctx, staticNames, allowedGroups) {
|
|
|
2379
2500
|
return result;
|
|
2380
2501
|
}
|
|
2381
2502
|
async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups) {
|
|
2382
|
-
const { runsOn, excludeLabels } =
|
|
2503
|
+
const { include: runsOn, exclude: excludeLabels } = normalizeRunsOnToMatchers(job.runsOn, `generated job '${job.name}' runsOn`);
|
|
2383
2504
|
let resolvedEnvironment;
|
|
2384
2505
|
if (typeof job.environment === "function") {
|
|
2385
2506
|
const value = await withTimeout(() => job.environment(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic environment for generated job '${job.name}'`);
|
|
@@ -2402,7 +2523,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2402
2523
|
_type: "static",
|
|
2403
2524
|
name: job.name,
|
|
2404
2525
|
runsOn,
|
|
2405
|
-
...excludeLabels ? { excludeLabels } : {},
|
|
2526
|
+
...excludeLabels.length > 0 ? { excludeLabels } : {},
|
|
2406
2527
|
needs: resolvedNeeds,
|
|
2407
2528
|
...dependsOnGroups.length > 0 ? { dependsOnGroups } : {},
|
|
2408
2529
|
steps: serializeSteps(job.steps),
|
|
@@ -2416,20 +2537,6 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2416
2537
|
};
|
|
2417
2538
|
}
|
|
2418
2539
|
/**
|
|
2419
|
-
* Normalize the polymorphic RunsOn type into the lock file format.
|
|
2420
|
-
*/
|
|
2421
|
-
function normalizeRunsOn(runsOn) {
|
|
2422
|
-
if (typeof runsOn === "string") return { runsOn };
|
|
2423
|
-
if (Array.isArray(runsOn)) return { runsOn };
|
|
2424
|
-
const selector = runsOn;
|
|
2425
|
-
const labels = typeof selector.labels === "string" ? [selector.labels] : selector.labels;
|
|
2426
|
-
const exclude = selector.exclude ? typeof selector.exclude === "string" ? [selector.exclude] : selector.exclude : void 0;
|
|
2427
|
-
return {
|
|
2428
|
-
runsOn: labels,
|
|
2429
|
-
...exclude ? { excludeLabels: exclude } : {}
|
|
2430
|
-
};
|
|
2431
|
-
}
|
|
2432
|
-
/**
|
|
2433
2540
|
* Resolve needs references. Jobs can reference other jobs by name (string),
|
|
2434
2541
|
* Job object reference, DynamicGroupRef, or NeedsEntry/NeedsGroupEntry objects.
|
|
2435
2542
|
* Validates against generatedNames union staticNames union allowedGroups.
|
|
@@ -2515,7 +2622,7 @@ async function serializeMatrix(matrix, jobName, runsOn, ctx) {
|
|
|
2515
2622
|
workflow: { name: ctx.workflowName },
|
|
2516
2623
|
job: {
|
|
2517
2624
|
name: jobName,
|
|
2518
|
-
runsOn:
|
|
2625
|
+
runsOn: runsOn.map((m) => m.kind === "exact" ? m.value : `/${m.source}/${m.flags}`)
|
|
2519
2626
|
}
|
|
2520
2627
|
},
|
|
2521
2628
|
log: ctx.log,
|
|
@@ -2541,6 +2648,7 @@ var MatrixExpansionError, DYNAMIC_FIELD_TIMEOUT_MS;
|
|
|
2541
2648
|
var init_dynamic_job_serializer = __esmMin((() => {
|
|
2542
2649
|
init_timeout_util();
|
|
2543
2650
|
MatrixExpansionError = class MatrixExpansionError extends Error {
|
|
2651
|
+
jobName;
|
|
2544
2652
|
name = "MatrixExpansionError";
|
|
2545
2653
|
constructor(jobName, message) {
|
|
2546
2654
|
super(message);
|
|
@@ -2814,7 +2922,7 @@ function decryptBuffer(encrypted, aesKey) {
|
|
|
2814
2922
|
*/
|
|
2815
2923
|
async function applyOverlay(config) {
|
|
2816
2924
|
const { tarballUrl, cliPublicKey, orchestratorPrivateKey, repoDir } = config;
|
|
2817
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
|
|
2925
|
+
const tmpDir = await fs$1.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
|
|
2818
2926
|
try {
|
|
2819
2927
|
logger$7.info("Downloading overlay tarball", { url: tarballUrl.replace(/\?.*$/, "?[redacted]") });
|
|
2820
2928
|
let encryptedData;
|
|
@@ -2828,7 +2936,7 @@ async function applyOverlay(config) {
|
|
|
2828
2936
|
const decryptedData = decryptBuffer(encryptedData, aesKey);
|
|
2829
2937
|
logger$7.info("Extracting overlay tarball", { size: decryptedData.length });
|
|
2830
2938
|
const extractDir = path.join(tmpDir, "extracted");
|
|
2831
|
-
await fs.mkdir(extractDir, { recursive: true });
|
|
2939
|
+
await fs$1.mkdir(extractDir, { recursive: true });
|
|
2832
2940
|
try {
|
|
2833
2941
|
const readable = Readable.from(decryptedData);
|
|
2834
2942
|
await new Promise((resolve, reject) => {
|
|
@@ -2843,7 +2951,7 @@ async function applyOverlay(config) {
|
|
|
2843
2951
|
const manifestPath = path.join(extractDir, ".kici-overlay-tmp", "manifest.json");
|
|
2844
2952
|
let manifestContent;
|
|
2845
2953
|
try {
|
|
2846
|
-
manifestContent = await fs.readFile(manifestPath, "utf-8");
|
|
2954
|
+
manifestContent = await fs$1.readFile(manifestPath, "utf-8");
|
|
2847
2955
|
} catch {
|
|
2848
2956
|
throw new Error("Overlay manifest not found: expected .kici-overlay-tmp/manifest.json in tarball");
|
|
2849
2957
|
}
|
|
@@ -2864,15 +2972,15 @@ async function applyOverlay(config) {
|
|
|
2864
2972
|
for (const file of checksumFiles) {
|
|
2865
2973
|
const srcPath = path.join(extractDir, file);
|
|
2866
2974
|
const destPath = path.join(repoDir, file);
|
|
2867
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
2868
|
-
await fs.copyFile(srcPath, destPath);
|
|
2975
|
+
await fs$1.mkdir(path.dirname(destPath), { recursive: true });
|
|
2976
|
+
await fs$1.copyFile(srcPath, destPath);
|
|
2869
2977
|
filesApplied++;
|
|
2870
2978
|
}
|
|
2871
2979
|
let filesDeleted = 0;
|
|
2872
2980
|
for (const file of manifest.deletions) {
|
|
2873
2981
|
const targetPath = path.join(repoDir, file);
|
|
2874
2982
|
try {
|
|
2875
|
-
await fs.unlink(targetPath);
|
|
2983
|
+
await fs$1.unlink(targetPath);
|
|
2876
2984
|
filesDeleted++;
|
|
2877
2985
|
} catch {
|
|
2878
2986
|
logger$7.debug("Deletion target not found, skipping", { file });
|
|
@@ -2888,7 +2996,7 @@ async function applyOverlay(config) {
|
|
|
2888
2996
|
verified: true
|
|
2889
2997
|
};
|
|
2890
2998
|
} finally {
|
|
2891
|
-
await fs.rm(tmpDir, {
|
|
2999
|
+
await fs$1.rm(tmpDir, {
|
|
2892
3000
|
recursive: true,
|
|
2893
3001
|
force: true
|
|
2894
3002
|
}).catch(() => {});
|
|
@@ -2941,7 +3049,7 @@ function noopResult() {
|
|
|
2941
3049
|
};
|
|
2942
3050
|
}
|
|
2943
3051
|
/** Build the synthesized env-var name for registry index `i`. */
|
|
2944
|
-
function tokenEnvName(jobIdShort, index) {
|
|
3052
|
+
function tokenEnvName$1(jobIdShort, index) {
|
|
2945
3053
|
return `KICI_NPM_TOKEN_${jobIdShort}_${index}`;
|
|
2946
3054
|
}
|
|
2947
3055
|
/** Render the agent-managed block of `.npmrc` lines. */
|
|
@@ -2950,7 +3058,7 @@ function renderAgentLines(registries, jobIdShort) {
|
|
|
2950
3058
|
const lines = [];
|
|
2951
3059
|
for (let i = 0; i < registries.length; i++) {
|
|
2952
3060
|
const reg = registries[i];
|
|
2953
|
-
const envVar = tokenEnvName(jobIdShort, i);
|
|
3061
|
+
const envVar = tokenEnvName$1(jobIdShort, i);
|
|
2954
3062
|
const authKey = reg.url.replace(/^https?:/, "");
|
|
2955
3063
|
if (reg.scope) lines.push(`${reg.scope}:registry=${reg.url}`);
|
|
2956
3064
|
else lines.push(`registry=${reg.url}`);
|
|
@@ -2982,7 +3090,7 @@ async function applyNpmRegistryConfig(args) {
|
|
|
2982
3090
|
const tokenEnv = {};
|
|
2983
3091
|
const tokensForRedaction = [];
|
|
2984
3092
|
for (let i = 0; i < registries.length; i++) {
|
|
2985
|
-
tokenEnv[tokenEnvName(args.jobIdShort, i)] = registries[i].token;
|
|
3093
|
+
tokenEnv[tokenEnvName$1(args.jobIdShort, i)] = registries[i].token;
|
|
2986
3094
|
tokensForRedaction.push(registries[i].token);
|
|
2987
3095
|
}
|
|
2988
3096
|
for (const value of Object.values(installEnvSecrets)) if (value) tokensForRedaction.push(value);
|
|
@@ -3020,6 +3128,113 @@ function redactNpmOutput(input, tokens) {
|
|
|
3020
3128
|
}
|
|
3021
3129
|
var init_npm_registry_config = __esmMin((() => {}));
|
|
3022
3130
|
//#endregion
|
|
3131
|
+
//#region src/execution/yarnrc-berry-config.ts
|
|
3132
|
+
/**
|
|
3133
|
+
* Apply yarn-berry registry auth + a forced `nodeLinker: node-modules` to a
|
|
3134
|
+
* workflow's `.kici/.yarnrc.yml` for the lifetime of one `yarn install`, then
|
|
3135
|
+
* restore the file on cleanup. The berry analog of `npm-registry-config.ts`:
|
|
3136
|
+
* berry reads `.yarnrc.yml` (not `.npmrc`), so the auth block uses berry's
|
|
3137
|
+
* `npmRegistryServer` / `npmScopes` / `npmAuthToken` keys with `${VAR}`
|
|
3138
|
+
* env-var interpolation. Token bytes never reach disk — each registry token is
|
|
3139
|
+
* exposed as a job-scoped env var and the on-disk value is the `${VAR}`
|
|
3140
|
+
* reference.
|
|
3141
|
+
*
|
|
3142
|
+
* `nodeLinker: node-modules` makes berry lay down a real `node_modules` tree
|
|
3143
|
+
* (no PnP `.pnp.cjs`), so the agent's packer / restore / sibling-walk /
|
|
3144
|
+
* workflow-loader work unchanged. `enableScripts: false` (when a private
|
|
3145
|
+
* registry is configured) keeps dependency lifecycle scripts from seeing the
|
|
3146
|
+
* synthesized token env vars — the same security model as npm/pnpm/classic
|
|
3147
|
+
* `--ignore-scripts`.
|
|
3148
|
+
*
|
|
3149
|
+
* Reuses the same `ApplyNpmRegistryConfigArgs` / `ApplyNpmRegistryConfigResult`
|
|
3150
|
+
* shapes as the npm overlay so `dep-installer` can pick either by flavor.
|
|
3151
|
+
*/
|
|
3152
|
+
/** Build the synthesized env-var name for registry index `i`. */
|
|
3153
|
+
function tokenEnvName(jobIdShort, index) {
|
|
3154
|
+
return `KICI_NPM_TOKEN_${jobIdShort}_${index}`;
|
|
3155
|
+
}
|
|
3156
|
+
/** Read + parse an existing `.yarnrc.yml`, or `{}` when absent/empty. */
|
|
3157
|
+
async function readOriginalYarnrc(path) {
|
|
3158
|
+
try {
|
|
3159
|
+
const raw = await readFile(path, "utf8");
|
|
3160
|
+
return {
|
|
3161
|
+
raw,
|
|
3162
|
+
doc: parse(raw) ?? {}
|
|
3163
|
+
};
|
|
3164
|
+
} catch (err) {
|
|
3165
|
+
if (err.code === "ENOENT") return {
|
|
3166
|
+
raw: null,
|
|
3167
|
+
doc: {}
|
|
3168
|
+
};
|
|
3169
|
+
throw err;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
function buildRegistryBlock(envVar, url, alwaysAuth) {
|
|
3173
|
+
return {
|
|
3174
|
+
npmRegistryServer: url,
|
|
3175
|
+
npmAuthToken: `\${${envVar}}`,
|
|
3176
|
+
...alwaysAuth ? { npmAlwaysAuth: true } : {}
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
async function applyYarnrcBerryConfig(args) {
|
|
3180
|
+
const registries = args.npmRegistries ?? [];
|
|
3181
|
+
const installEnvSecrets = args.installEnvSecrets ?? {};
|
|
3182
|
+
const hasPrivateRegistry = registries.length > 0 || Object.keys(installEnvSecrets).length > 0;
|
|
3183
|
+
const yarnrcPath = join(args.kiciDir, ".yarnrc.yml");
|
|
3184
|
+
const { raw: original, doc } = await readOriginalYarnrc(yarnrcPath);
|
|
3185
|
+
const cacheFolder = await mkdtemp(join(tmpdir(), "kici-yarn-berry-cache-"));
|
|
3186
|
+
const merged = {
|
|
3187
|
+
...doc,
|
|
3188
|
+
nodeLinker: "node-modules",
|
|
3189
|
+
enableGlobalCache: false,
|
|
3190
|
+
cacheFolder
|
|
3191
|
+
};
|
|
3192
|
+
const tokenEnv = {};
|
|
3193
|
+
const tokensForRedaction = [];
|
|
3194
|
+
if (hasPrivateRegistry) {
|
|
3195
|
+
merged.enableScripts = false;
|
|
3196
|
+
const npmScopes = { ...doc.npmScopes ?? {} };
|
|
3197
|
+
for (let i = 0; i < registries.length; i++) {
|
|
3198
|
+
const reg = registries[i];
|
|
3199
|
+
const envVar = tokenEnvName(args.jobIdShort, i);
|
|
3200
|
+
tokenEnv[envVar] = reg.token;
|
|
3201
|
+
tokensForRedaction.push(reg.token);
|
|
3202
|
+
const block = buildRegistryBlock(envVar, reg.url, reg.alwaysAuth);
|
|
3203
|
+
if (reg.scope) npmScopes[reg.scope] = block;
|
|
3204
|
+
else {
|
|
3205
|
+
merged.npmRegistryServer = reg.url;
|
|
3206
|
+
merged.npmAuthToken = block.npmAuthToken;
|
|
3207
|
+
if (reg.alwaysAuth) merged.npmAlwaysAuth = true;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (Object.keys(npmScopes).length > 0) merged.npmScopes = npmScopes;
|
|
3211
|
+
for (const value of Object.values(installEnvSecrets)) if (value) tokensForRedaction.push(value);
|
|
3212
|
+
}
|
|
3213
|
+
await writeFile(yarnrcPath, stringify(merged), {
|
|
3214
|
+
encoding: "utf8",
|
|
3215
|
+
mode: 384
|
|
3216
|
+
});
|
|
3217
|
+
const cleanup = async () => {
|
|
3218
|
+
try {
|
|
3219
|
+
if (original === null) await unlink(yarnrcPath).catch(() => {});
|
|
3220
|
+
else await writeFile(yarnrcPath, original, { encoding: "utf8" });
|
|
3221
|
+
} catch {}
|
|
3222
|
+
await rm(cacheFolder, {
|
|
3223
|
+
recursive: true,
|
|
3224
|
+
force: true
|
|
3225
|
+
}).catch(() => {});
|
|
3226
|
+
};
|
|
3227
|
+
return {
|
|
3228
|
+
extraEnv: {
|
|
3229
|
+
...installEnvSecrets,
|
|
3230
|
+
...tokenEnv
|
|
3231
|
+
},
|
|
3232
|
+
tokensForRedaction,
|
|
3233
|
+
cleanup
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
var init_yarnrc_berry_config = __esmMin((() => {}));
|
|
3237
|
+
//#endregion
|
|
3023
3238
|
//#region src/execution/validate-kici-deps.ts
|
|
3024
3239
|
/**
|
|
3025
3240
|
* Pre-install validation for `.kici/` dependency specifiers.
|
|
@@ -3037,6 +3252,13 @@ var init_npm_registry_config = __esmMin((() => {}));
|
|
|
3037
3252
|
* clones the whole repo, so an in-repo sibling is present), and resolves
|
|
3038
3253
|
* `file:`/`link:`/`portal:` against a path — allowed when that path stays
|
|
3039
3254
|
* inside the cloned repo, rejected when it escapes the clone.
|
|
3255
|
+
* - yarn classic (v1) has no `workspace:` protocol and no `portal:` — it links
|
|
3256
|
+
* in-repo siblings by version range, not by a local specifier — so both are
|
|
3257
|
+
* rejected with guidance; `file:`/`link:` are allowed when the path stays
|
|
3258
|
+
* inside the clone, rejected when it escapes.
|
|
3259
|
+
* - yarn berry (v2+) resolves `workspace:` against the repo-root package.json
|
|
3260
|
+
* `workspaces` field and `portal:`/`file:`/`link:` against inside-repo paths,
|
|
3261
|
+
* so those are allowed when present/inside the clone and rejected otherwise.
|
|
3040
3262
|
*
|
|
3041
3263
|
* This module performs that classification so unresolvable specifiers fail
|
|
3042
3264
|
* fast with guidance rather than a cryptic install error.
|
|
@@ -3096,6 +3318,17 @@ async function fileExists$1(target) {
|
|
|
3096
3318
|
return false;
|
|
3097
3319
|
}
|
|
3098
3320
|
}
|
|
3321
|
+
/** Whether the repo-root package.json declares a non-empty `workspaces` array. */
|
|
3322
|
+
async function rootHasWorkspaces(repoRoot) {
|
|
3323
|
+
try {
|
|
3324
|
+
const ws = JSON.parse(await readFile(join(repoRoot, "package.json"), "utf-8")).workspaces;
|
|
3325
|
+
if (Array.isArray(ws)) return ws.length > 0;
|
|
3326
|
+
if (ws && typeof ws === "object" && Array.isArray(ws.packages)) return ws.packages.length > 0;
|
|
3327
|
+
return false;
|
|
3328
|
+
} catch {
|
|
3329
|
+
return false;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3099
3332
|
/** Resolve a `file:`/`link:`/`portal:` spec to an absolute path under kiciDir. */
|
|
3100
3333
|
function resolveLocalPath(kiciDir, dep) {
|
|
3101
3334
|
const rawPath = dep.spec.slice(dep.protocol.length);
|
|
@@ -3110,8 +3343,31 @@ function isInsideRepo(repoRoot, target) {
|
|
|
3110
3343
|
* Classify each local-protocol dependency for the detected package manager and
|
|
3111
3344
|
* return the ones that are unresolvable in the agent's single-clone model.
|
|
3112
3345
|
*/
|
|
3113
|
-
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
3346
|
+
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot, yarnFlavor) {
|
|
3114
3347
|
if (packageManager === PackageManager.Npm) return [...deps];
|
|
3348
|
+
if (packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry) {
|
|
3349
|
+
const hasWorkspaces = await rootHasWorkspaces(repoRoot);
|
|
3350
|
+
const unresolvable = [];
|
|
3351
|
+
for (const dep of deps) {
|
|
3352
|
+
if (dep.protocol === "workspace:") {
|
|
3353
|
+
if (!hasWorkspaces) unresolvable.push(dep);
|
|
3354
|
+
continue;
|
|
3355
|
+
}
|
|
3356
|
+
if (!isInsideRepo(repoRoot, resolveLocalPath(kiciDir, dep))) unresolvable.push(dep);
|
|
3357
|
+
}
|
|
3358
|
+
return unresolvable;
|
|
3359
|
+
}
|
|
3360
|
+
if (packageManager === PackageManager.Yarn) {
|
|
3361
|
+
const unresolvable = [];
|
|
3362
|
+
for (const dep of deps) {
|
|
3363
|
+
if (dep.protocol === "workspace:" || dep.protocol === "portal:") {
|
|
3364
|
+
unresolvable.push(dep);
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
if (!isInsideRepo(repoRoot, resolveLocalPath(kiciDir, dep))) unresolvable.push(dep);
|
|
3368
|
+
}
|
|
3369
|
+
return unresolvable;
|
|
3370
|
+
}
|
|
3115
3371
|
const hasWorkspaceFile = await fileExists$1(join(repoRoot, "pnpm-workspace.yaml"));
|
|
3116
3372
|
const unresolvable = [];
|
|
3117
3373
|
for (const dep of deps) {
|
|
@@ -3124,9 +3380,11 @@ async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
|
3124
3380
|
return unresolvable;
|
|
3125
3381
|
}
|
|
3126
3382
|
/** Build the actionable error for unresolvable local-protocol dependencies. */
|
|
3127
|
-
function formatUnresolvableDepError(offenders, packageManager) {
|
|
3383
|
+
function formatUnresolvableDepError(offenders, packageManager, yarnFlavor) {
|
|
3128
3384
|
const list = offenders.map((o) => `${o.name}: ${o.spec}`).join(", ");
|
|
3129
3385
|
if (packageManager === PackageManager.Npm) return `These .kici/ dependencies use local-protocol specifiers npm cannot resolve from a registry: ${list}. npm has no workspace protocol — pin a published version, publish the package to your registry, or use pnpm so an in-repo workspace sibling can be resolved.`;
|
|
3386
|
+
if (packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry) return `These .kici/ dependencies cannot be resolved by yarn berry from the cloned repository: ${list}. A workspace: dependency requires a "workspaces" array in the repo-root package.json, and file:/link:/portal: paths must stay inside this repository.`;
|
|
3387
|
+
if (packageManager === PackageManager.Yarn) return `These .kici/ dependencies use specifiers yarn classic cannot resolve: ${list}. yarn classic has no workspace: or portal: protocol — reference an in-repo sibling by a version range (yarn links matching workspace members), use pnpm, or keep file:/link: paths inside this repository. (yarn berry support requires a yarn@2+ packageManager field or a .yarnrc.yml.)`;
|
|
3130
3388
|
return `These .kici/ dependencies point outside the cloned repository, which the agent never has: ${list}. A workspace: dependency requires a pnpm-workspace.yaml at the repo root, and file:/link:/portal: paths must stay inside this repository.`;
|
|
3131
3389
|
}
|
|
3132
3390
|
/**
|
|
@@ -3140,9 +3398,10 @@ async function assertResolvableDeps(args) {
|
|
|
3140
3398
|
if (!pkg) return;
|
|
3141
3399
|
const localDeps = findLocalProtocolDeps(pkg);
|
|
3142
3400
|
if (localDeps.length === 0) return;
|
|
3143
|
-
const
|
|
3401
|
+
const flavor = args.yarnFlavor ?? YarnFlavor.Classic;
|
|
3402
|
+
const offenders = await findUnresolvableDeps(localDeps, args.packageManager, args.kiciDir, args.repoRoot, flavor);
|
|
3144
3403
|
if (offenders.length === 0) return;
|
|
3145
|
-
throw new Error(formatUnresolvableDepError(offenders, args.packageManager));
|
|
3404
|
+
throw new Error(formatUnresolvableDepError(offenders, args.packageManager, flavor));
|
|
3146
3405
|
}
|
|
3147
3406
|
var LOCAL_PROTOCOLS, DEP_FIELDS;
|
|
3148
3407
|
var init_validate_kici_deps = __esmMin((() => {
|
|
@@ -3160,6 +3419,102 @@ var init_validate_kici_deps = __esmMin((() => {
|
|
|
3160
3419
|
];
|
|
3161
3420
|
}));
|
|
3162
3421
|
//#endregion
|
|
3422
|
+
//#region src/execution/workspace-siblings.ts
|
|
3423
|
+
/**
|
|
3424
|
+
* In-repo workspace-sibling discovery for the agent's dependency handling.
|
|
3425
|
+
*
|
|
3426
|
+
* A pnpm or yarn-classic workspace lays out a `.kici/` member's `workspace:`
|
|
3427
|
+
* (pnpm) or version-range (yarn) siblings as symlinks pointing at package
|
|
3428
|
+
* directories that live inside the clone but outside `.kici/` and outside the
|
|
3429
|
+
* `node_modules` store. The dep-cache packer must travel those sibling dirs with
|
|
3430
|
+
* the closure (their symlinks would dangle otherwise), and the yarn install path
|
|
3431
|
+
* must build them (the install links a sibling but does not build it).
|
|
3432
|
+
*
|
|
3433
|
+
* `collectInRepoSiblings` walks a starting `node_modules` (and transitively each
|
|
3434
|
+
* discovered sibling's `node_modules`), returning each in-repo sibling directory
|
|
3435
|
+
* once, repo-root-relative, in breadth-first discovery order. The starting
|
|
3436
|
+
* `node_modules` is a parameter so it serves pnpm + yarn-standalone (seeded at
|
|
3437
|
+
* `.kici/node_modules`) and yarn-workspace-member (seeded at the hoisted root
|
|
3438
|
+
* `node_modules`).
|
|
3439
|
+
*/
|
|
3440
|
+
/**
|
|
3441
|
+
* The directory yarn lays `.kici`'s dependencies into. A standalone `.kici`
|
|
3442
|
+
* (own lockfile, no parent workspace) gets `.kici/node_modules`; a workspace
|
|
3443
|
+
* member hoists everything to the repo-root `node_modules`, leaving no
|
|
3444
|
+
* `.kici/node_modules`.
|
|
3445
|
+
*/
|
|
3446
|
+
function resolveYarnNodeModulesRoot(repoRoot, kiciDir) {
|
|
3447
|
+
const kiciNm = join(kiciDir, "node_modules");
|
|
3448
|
+
return existsSync(kiciNm) ? kiciNm : join(repoRoot, "node_modules");
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* Walk `seedNodeModules` (and transitively each in-repo sibling's
|
|
3452
|
+
* `node_modules`) collecting the repo-root-relative directories of workspace
|
|
3453
|
+
* siblings — package dirs that live inside the clone but outside `.kici/` and
|
|
3454
|
+
* outside the repo-root `node_modules/` store. Returns each dir once, in
|
|
3455
|
+
* discovery (BFS) order.
|
|
3456
|
+
*/
|
|
3457
|
+
async function collectInRepoSiblings(workDir, kiciDir, seedNodeModules = join(kiciDir, "node_modules")) {
|
|
3458
|
+
const repoRoot = resolve(workDir);
|
|
3459
|
+
const kiciResolved = resolve(kiciDir);
|
|
3460
|
+
const rootNodeModules = resolve(join(workDir, "node_modules"));
|
|
3461
|
+
const found = /* @__PURE__ */ new Set();
|
|
3462
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3463
|
+
const queue = [seedNodeModules];
|
|
3464
|
+
while (queue.length > 0) {
|
|
3465
|
+
const nmDir = queue.shift();
|
|
3466
|
+
const real = await realpath(nmDir).catch(() => null);
|
|
3467
|
+
if (!real || visited.has(real)) continue;
|
|
3468
|
+
visited.add(real);
|
|
3469
|
+
for (const target of await resolveNodeModulesLinks(nmDir)) {
|
|
3470
|
+
if (!isInside(repoRoot, target)) continue;
|
|
3471
|
+
if (isInside(kiciResolved, target) || isInside(rootNodeModules, target)) continue;
|
|
3472
|
+
const rel = relative(workDir, target);
|
|
3473
|
+
if (!found.has(rel)) {
|
|
3474
|
+
found.add(rel);
|
|
3475
|
+
queue.push(join(target, "node_modules"));
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
return [...found];
|
|
3480
|
+
}
|
|
3481
|
+
/** Resolve every package symlink target under a `node_modules` dir (descending one level into `@scope` dirs). */
|
|
3482
|
+
async function resolveNodeModulesLinks(nmDir) {
|
|
3483
|
+
const targets = [];
|
|
3484
|
+
for (const entry of await readdir(nmDir).catch(() => [])) {
|
|
3485
|
+
if (entry.startsWith(".")) continue;
|
|
3486
|
+
const entryPath = join(nmDir, entry);
|
|
3487
|
+
if (entry.startsWith("@")) {
|
|
3488
|
+
for (const scoped of await readdir(entryPath).catch(() => [])) {
|
|
3489
|
+
const target = await resolveIfSymlink(join(entryPath, scoped));
|
|
3490
|
+
if (target) targets.push(target);
|
|
3491
|
+
}
|
|
3492
|
+
continue;
|
|
3493
|
+
}
|
|
3494
|
+
const target = await resolveIfSymlink(entryPath);
|
|
3495
|
+
if (target) targets.push(target);
|
|
3496
|
+
}
|
|
3497
|
+
return targets;
|
|
3498
|
+
}
|
|
3499
|
+
/** Return the real path of `p` if it is a symlink, else null. */
|
|
3500
|
+
async function resolveIfSymlink(p) {
|
|
3501
|
+
try {
|
|
3502
|
+
if (!(await lstat(p)).isSymbolicLink()) return null;
|
|
3503
|
+
return await realpath(p);
|
|
3504
|
+
} catch {
|
|
3505
|
+
return null;
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
/** Whether `target` is `root` itself or a path inside it. */
|
|
3509
|
+
function isInside(root, target) {
|
|
3510
|
+
const rel = relative(root, target);
|
|
3511
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith(`..${sep}`) && !isAbsoluteRel(rel);
|
|
3512
|
+
}
|
|
3513
|
+
function isAbsoluteRel(rel) {
|
|
3514
|
+
return rel.length > 1 && rel[1] === ":";
|
|
3515
|
+
}
|
|
3516
|
+
var init_workspace_siblings = __esmMin((() => {}));
|
|
3517
|
+
//#endregion
|
|
3163
3518
|
//#region src/execution/dep-installer.ts
|
|
3164
3519
|
/**
|
|
3165
3520
|
* Inline dependency installation for graceful degradation.
|
|
@@ -3167,12 +3522,17 @@ var init_validate_kici_deps = __esmMin((() => {
|
|
|
3167
3522
|
* When the dep cache is unavailable or a download fails, the agent installs
|
|
3168
3523
|
* `.kici/` dependencies directly with the repository's package manager.
|
|
3169
3524
|
*
|
|
3170
|
-
* The package manager is detected from the cloned repo (npm / pnpm); the
|
|
3525
|
+
* The package manager is detected from the cloned repo (npm / pnpm / yarn); the
|
|
3171
3526
|
* presence of `.kici/package.json` signals that deps should be installed. npm
|
|
3172
3527
|
* is the default and ships with every Node.js install; pnpm is used when the
|
|
3173
3528
|
* repo is a pnpm workspace so a `.kici/` member can resolve in-repo
|
|
3174
|
-
* `workspace:` siblings. yarn is
|
|
3175
|
-
*
|
|
3529
|
+
* `workspace:` siblings. yarn is supported in both flavors: classic (v1) reads
|
|
3530
|
+
* `.kici/.npmrc` for registry auth and links version-range workspace siblings;
|
|
3531
|
+
* berry (v2+) reads a synthesized `.kici/.yarnrc.yml` for auth, runs with a
|
|
3532
|
+
* forced `nodeLinker: node-modules` (so the resulting tree matches classic/npm
|
|
3533
|
+
* and the runner's plain node resolution holds), and resolves
|
|
3534
|
+
* `workspace:`/`portal:` siblings. Either flavor links the sibling but does not
|
|
3535
|
+
* build it, so the agent builds the in-repo closure after install.
|
|
3176
3536
|
*
|
|
3177
3537
|
* Security: the install runs with an isolated per-invocation cache/store
|
|
3178
3538
|
* directory to prevent cache poisoning across build jobs — a malicious
|
|
@@ -3193,6 +3553,15 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
3193
3553
|
return await detectPackageManagerFromManifests(repoRoot) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
3194
3554
|
}
|
|
3195
3555
|
/**
|
|
3556
|
+
* Detect the yarn flavor (classic vs berry) for the cloned repo. Mirrors
|
|
3557
|
+
* `detectKiciPackageManager`: probe the repo root first, then `.kici/` for a
|
|
3558
|
+
* standalone project. Only called when the detected manager is `Yarn`.
|
|
3559
|
+
*/
|
|
3560
|
+
async function detectKiciYarnFlavor(repoRoot, kiciDir) {
|
|
3561
|
+
if (await detectYarnFlavor(repoRoot) === YarnFlavor.Berry) return YarnFlavor.Berry;
|
|
3562
|
+
return detectYarnFlavor(kiciDir);
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3196
3565
|
* Install `.kici/` dependencies inline with the repo's package manager.
|
|
3197
3566
|
*
|
|
3198
3567
|
* Falls back to this when the dep cache is unavailable or a download fails.
|
|
@@ -3211,20 +3580,28 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
3211
3580
|
async function installDeps(kiciDir, opts = {}) {
|
|
3212
3581
|
const repoRoot = opts.repoRoot ?? dirname(kiciDir);
|
|
3213
3582
|
const packageManager = await detectKiciPackageManager(repoRoot, kiciDir);
|
|
3583
|
+
const yarnFlavor = packageManager === PackageManager.Yarn ? await detectKiciYarnFlavor(repoRoot, kiciDir) : YarnFlavor.Classic;
|
|
3214
3584
|
logger$6.info("Installing deps inline", {
|
|
3215
3585
|
packageManager,
|
|
3586
|
+
yarnFlavor,
|
|
3216
3587
|
dir: kiciDir
|
|
3217
3588
|
});
|
|
3218
|
-
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, cwd=${kiciDir}\n`);
|
|
3219
|
-
if (packageManager === PackageManager.Yarn) throw new Error("This repository uses yarn, which the KiCI agent does not yet support for .kici/ dependency installation. Use npm or pnpm for the .kici/ project, or open a feature request for yarn support.");
|
|
3589
|
+
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, flavor=${yarnFlavor}, cwd=${kiciDir}\n`);
|
|
3220
3590
|
await assertResolvableDeps({
|
|
3221
3591
|
kiciDir,
|
|
3222
3592
|
repoRoot,
|
|
3223
|
-
packageManager
|
|
3593
|
+
packageManager,
|
|
3594
|
+
yarnFlavor
|
|
3224
3595
|
});
|
|
3225
3596
|
const startTime = Date.now();
|
|
3226
3597
|
const hasPrivateRegistry = (opts.npmRegistries?.length ?? 0) > 0 || (opts.installEnvSecrets ? Object.keys(opts.installEnvSecrets).length > 0 : false);
|
|
3227
|
-
const
|
|
3598
|
+
const isBerry = packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry;
|
|
3599
|
+
const registryConfig = isBerry ? await applyYarnrcBerryConfig({
|
|
3600
|
+
kiciDir,
|
|
3601
|
+
npmRegistries: opts.npmRegistries,
|
|
3602
|
+
installEnvSecrets: opts.installEnvSecrets,
|
|
3603
|
+
jobIdShort: opts.jobIdShort ?? "00000000"
|
|
3604
|
+
}) : await applyNpmRegistryConfig({
|
|
3228
3605
|
kiciDir,
|
|
3229
3606
|
npmRegistries: opts.npmRegistries,
|
|
3230
3607
|
installEnvSecrets: opts.installEnvSecrets,
|
|
@@ -3236,6 +3613,15 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
3236
3613
|
hasPrivateRegistry,
|
|
3237
3614
|
registryConfig
|
|
3238
3615
|
});
|
|
3616
|
+
else if (isBerry) await runYarnBerryInstall({
|
|
3617
|
+
kiciDir,
|
|
3618
|
+
registryConfig
|
|
3619
|
+
});
|
|
3620
|
+
else if (packageManager === PackageManager.Yarn) await runYarnInstall({
|
|
3621
|
+
kiciDir,
|
|
3622
|
+
hasPrivateRegistry,
|
|
3623
|
+
registryConfig
|
|
3624
|
+
});
|
|
3239
3625
|
else await runNpmInstall({
|
|
3240
3626
|
kiciDir,
|
|
3241
3627
|
hasPrivateRegistry,
|
|
@@ -3250,6 +3636,7 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
3250
3636
|
await registryConfig.cleanup();
|
|
3251
3637
|
}
|
|
3252
3638
|
if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
|
|
3639
|
+
if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor);
|
|
3253
3640
|
const durationMs = Date.now() - startTime;
|
|
3254
3641
|
process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
|
|
3255
3642
|
logger$6.info("Deps installed inline", {
|
|
@@ -3337,6 +3724,131 @@ async function runPnpmInstall(args) {
|
|
|
3337
3724
|
}).catch(() => {});
|
|
3338
3725
|
}
|
|
3339
3726
|
}
|
|
3727
|
+
/** Pure: argv for `yarn install` with an isolated cache folder. */
|
|
3728
|
+
function buildYarnInstallArgs(cacheDir, hasPrivateRegistry) {
|
|
3729
|
+
const a = [
|
|
3730
|
+
"install",
|
|
3731
|
+
"--cache-folder",
|
|
3732
|
+
cacheDir,
|
|
3733
|
+
"--non-interactive",
|
|
3734
|
+
"--no-progress"
|
|
3735
|
+
];
|
|
3736
|
+
if (hasPrivateRegistry) a.push("--ignore-scripts");
|
|
3737
|
+
return a;
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Run `yarn install` from `.kici/` with an isolated cache folder. yarn classic
|
|
3741
|
+
* reads the synthesized `.kici/.npmrc` (registry + `${VAR}` token expansion) for
|
|
3742
|
+
* private-registry auth. A workspace member hoists deps to the repo-root
|
|
3743
|
+
* node_modules; a standalone `.kici` gets `.kici/node_modules`. Not
|
|
3744
|
+
* `--frozen-lockfile` (resolved URLs in the lockfile may point at a different
|
|
3745
|
+
* registry than the synthesized `.npmrc`, e.g. localhost tunnel vs direct IP).
|
|
3746
|
+
*/
|
|
3747
|
+
async function runYarnInstall(args) {
|
|
3748
|
+
await assertYarnAvailable();
|
|
3749
|
+
const { nodeDir } = resolveNpm();
|
|
3750
|
+
const cacheDir = await mkdtemp(join(tmpdir(), "kici-yarn-cache-"));
|
|
3751
|
+
const env = envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir);
|
|
3752
|
+
const argv = buildYarnInstallArgs(cacheDir, args.hasPrivateRegistry);
|
|
3753
|
+
try {
|
|
3754
|
+
process.stderr.write(`[dep-installer:trace] running: yarn ${argv.join(" ")}\n`);
|
|
3755
|
+
await execFileAsync("yarn", argv, {
|
|
3756
|
+
cwd: args.kiciDir,
|
|
3757
|
+
env,
|
|
3758
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3759
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3760
|
+
});
|
|
3761
|
+
} finally {
|
|
3762
|
+
await rm(cacheDir, {
|
|
3763
|
+
recursive: true,
|
|
3764
|
+
force: true
|
|
3765
|
+
}).catch(() => {});
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
/** Pure: argv for a berry `yarn install`. Cache + linker live in .yarnrc.yml. */
|
|
3769
|
+
function buildYarnBerryInstallArgs() {
|
|
3770
|
+
return ["install"];
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Run a berry `yarn install` from `.kici/`. The synthesized `.kici/.yarnrc.yml`
|
|
3774
|
+
* (applied by `applyYarnrcBerryConfig`) forces `nodeLinker: node-modules`, an
|
|
3775
|
+
* isolated `cacheFolder`, and — when a private registry is configured —
|
|
3776
|
+
* `enableScripts: false` + `npmScopes`/`npmRegistryServer` auth. corepack
|
|
3777
|
+
* provisions the repo-pinned berry version; `COREPACK_ENABLE_DOWNLOAD_PROMPT=0`
|
|
3778
|
+
* makes that non-interactive. Not `--immutable` (resolved URLs in the lockfile
|
|
3779
|
+
* may point at a different registry than the synthesized config).
|
|
3780
|
+
*/
|
|
3781
|
+
async function runYarnBerryInstall(args) {
|
|
3782
|
+
await assertYarnAvailable();
|
|
3783
|
+
const { nodeDir } = resolveNpm();
|
|
3784
|
+
const env = {
|
|
3785
|
+
...envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir),
|
|
3786
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
|
|
3787
|
+
};
|
|
3788
|
+
const argv = buildYarnBerryInstallArgs();
|
|
3789
|
+
process.stderr.write(`[dep-installer:trace] running: yarn ${argv.join(" ")} (berry)\n`);
|
|
3790
|
+
await execFileAsync("yarn", argv, {
|
|
3791
|
+
cwd: args.kiciDir,
|
|
3792
|
+
env,
|
|
3793
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3794
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
/** Throw an actionable error when the repo needs yarn but it is not installed. */
|
|
3798
|
+
async function assertYarnAvailable() {
|
|
3799
|
+
try {
|
|
3800
|
+
await execFileAsync("yarn", ["--version"], {
|
|
3801
|
+
timeout: 3e4,
|
|
3802
|
+
cwd: tmpdir()
|
|
3803
|
+
});
|
|
3804
|
+
} catch (e) {
|
|
3805
|
+
throw new Error(`This repository uses yarn, but yarn is not available on this agent. Install yarn (e.g. \`corepack enable\`) or run on a container/Firecracker agent that bundles it. (${toErrorMessage(e)})`);
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
/**
|
|
3809
|
+
* Build the in-repo workspace siblings `.kici` depends on (yarn links them on
|
|
3810
|
+
* install but does not build them). Walks siblings from the resolved
|
|
3811
|
+
* node_modules root and runs each sibling's `build` script in leaf-first
|
|
3812
|
+
* (reverse-discovery) order with a clean env (no synthesized registry tokens).
|
|
3813
|
+
* Deep cross-sibling build chains may build out of strict topological order —
|
|
3814
|
+
* real `.kici` closures are shallow.
|
|
3815
|
+
*/
|
|
3816
|
+
async function buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor) {
|
|
3817
|
+
const siblings = await collectInRepoSiblings(repoRoot, kiciDir, resolveYarnNodeModulesRoot(repoRoot, kiciDir));
|
|
3818
|
+
if (siblings.length === 0) return;
|
|
3819
|
+
const { nodeDir } = resolveNpm();
|
|
3820
|
+
const env = envWithNodeOnPath({}, nodeDir);
|
|
3821
|
+
for (const rel of [...siblings].reverse()) {
|
|
3822
|
+
const sibDir = join(repoRoot, rel);
|
|
3823
|
+
if (!await siblingHasBuildScript(sibDir)) continue;
|
|
3824
|
+
const [argv, cwd] = yarnFlavor === YarnFlavor.Berry ? [["run", "build"], sibDir] : [[
|
|
3825
|
+
"--cwd",
|
|
3826
|
+
sibDir,
|
|
3827
|
+
"run",
|
|
3828
|
+
"build"
|
|
3829
|
+
], repoRoot];
|
|
3830
|
+
process.stderr.write(`[dep-installer:trace] building yarn sibling (${yarnFlavor}): yarn ${argv.join(" ")} @ ${cwd}\n`);
|
|
3831
|
+
try {
|
|
3832
|
+
await execFileAsync("yarn", argv, {
|
|
3833
|
+
cwd,
|
|
3834
|
+
env,
|
|
3835
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3836
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
3837
|
+
});
|
|
3838
|
+
} catch (e) {
|
|
3839
|
+
logSubprocessStreams(e, []);
|
|
3840
|
+
throw new Error(`Failed to build .kici yarn workspace sibling ${rel}: ${describeExecError(e)}`);
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
/** Whether a sibling package.json declares a `build` script. */
|
|
3845
|
+
async function siblingHasBuildScript(sibDir) {
|
|
3846
|
+
try {
|
|
3847
|
+
return typeof JSON.parse(await readFile(join(sibDir, "package.json"), "utf-8")).scripts?.build === "string";
|
|
3848
|
+
} catch {
|
|
3849
|
+
return false;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3340
3852
|
/**
|
|
3341
3853
|
* Build the in-repo dependency closure of the `.kici/` package so a
|
|
3342
3854
|
* `workspace:` sibling's build output exists before the workflow that imports
|
|
@@ -3380,7 +3892,10 @@ function describeExecError(e) {
|
|
|
3380
3892
|
/** Throw an actionable error when the repo needs pnpm but it is not installed. */
|
|
3381
3893
|
async function assertPnpmAvailable() {
|
|
3382
3894
|
try {
|
|
3383
|
-
await execFileAsync("pnpm", ["--version"], {
|
|
3895
|
+
await execFileAsync("pnpm", ["--version"], {
|
|
3896
|
+
timeout: 3e4,
|
|
3897
|
+
cwd: tmpdir()
|
|
3898
|
+
});
|
|
3384
3899
|
} catch (e) {
|
|
3385
3900
|
throw new Error(`This repository is a pnpm workspace, but pnpm is not available on this agent. Install pnpm (e.g. \`corepack enable\`) or run on a container/ Firecracker agent that bundles it. (${toErrorMessage(e)})`);
|
|
3386
3901
|
}
|
|
@@ -3393,7 +3908,9 @@ function logSubprocessStreams(e, tokens) {
|
|
|
3393
3908
|
var logger$6, execFileAsync, INSTALL_TIMEOUT_MS, INSTALL_MAX_BUFFER;
|
|
3394
3909
|
var init_dep_installer = __esmMin((() => {
|
|
3395
3910
|
init_npm_registry_config();
|
|
3911
|
+
init_yarnrc_berry_config();
|
|
3396
3912
|
init_validate_kici_deps();
|
|
3913
|
+
init_workspace_siblings();
|
|
3397
3914
|
logger$6 = createLogger({ prefix: "dep-installer" });
|
|
3398
3915
|
execFileAsync = promisify(execFile);
|
|
3399
3916
|
INSTALL_TIMEOUT_MS = 6e5;
|
|
@@ -3409,13 +3926,21 @@ var init_dep_installer = __esmMin((() => {
|
|
|
3409
3926
|
* **repo-root-relative** (cwd = the clone root) so restore is a single layout
|
|
3410
3927
|
* regardless of package manager:
|
|
3411
3928
|
*
|
|
3412
|
-
* - npm
|
|
3929
|
+
* - npm: just `.kici/node_modules`.
|
|
3413
3930
|
* - pnpm: `.kici/node_modules` plus the repo-root `node_modules/.pnpm` virtual
|
|
3414
3931
|
* store and the in-repo `workspace:` sibling package directories `.kici`
|
|
3415
3932
|
* depends on (with their built output). pnpm lays `.kici/node_modules` out as
|
|
3416
3933
|
* symlinks into the root store and into sibling dirs that live outside
|
|
3417
3934
|
* `.kici/`, so packing `.kici/node_modules` alone would capture dangling
|
|
3418
3935
|
* links — the store and siblings must travel together.
|
|
3936
|
+
* - yarn (classic + berry): the resolved node_modules root (standalone `.kici`
|
|
3937
|
+
* → `.kici/node_modules`; hoisted workspace member → the repo-root
|
|
3938
|
+
* `node_modules`) plus the in-repo sibling package directories `.kici` depends
|
|
3939
|
+
* on (with their built output), whose symlinks would dangle otherwise. Berry
|
|
3940
|
+
* runs with a forced `nodeLinker: node-modules`, so its tree has the same
|
|
3941
|
+
* node_modules shape as classic and is packed identically (the flavor only
|
|
3942
|
+
* changes how siblings are referenced — version range vs `workspace:`/`portal:`
|
|
3943
|
+
* — not the packed layout).
|
|
3419
3944
|
*
|
|
3420
3945
|
* Uses tar.gz (Node.js built-in zlib, no external binary) in portable mode to
|
|
3421
3946
|
* strip user/group info for cross-machine consistency; symlinks are preserved
|
|
@@ -3429,9 +3954,10 @@ var init_dep_installer = __esmMin((() => {
|
|
|
3429
3954
|
* @throws Error if `.kici/node_modules` does not exist.
|
|
3430
3955
|
*/
|
|
3431
3956
|
async function packNodeModules(kiciDir) {
|
|
3432
|
-
if (!existsSync(join(kiciDir, "node_modules"))) throw new Error(`node_modules not found at ${join(kiciDir, "node_modules")}`);
|
|
3433
3957
|
const workDir = dirname(kiciDir);
|
|
3434
3958
|
const packageManager = await detectPackageManagerFromManifests(workDir) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
3959
|
+
const nmRoot = packageManager === PackageManager.Yarn ? resolveYarnNodeModulesRoot(workDir, kiciDir) : join(kiciDir, "node_modules");
|
|
3960
|
+
if (!existsSync(nmRoot)) throw new Error(`node_modules not found at ${nmRoot}`);
|
|
3435
3961
|
const entries = await closureEntries(workDir, kiciDir, packageManager);
|
|
3436
3962
|
logger$5.info("Packing dependency closure into tarball", {
|
|
3437
3963
|
dir: workDir,
|
|
@@ -3460,85 +3986,32 @@ async function packNodeModules(kiciDir) {
|
|
|
3460
3986
|
};
|
|
3461
3987
|
}
|
|
3462
3988
|
/**
|
|
3463
|
-
* Compute the repo-root-relative tar entries for the dependency closure. npm
|
|
3464
|
-
*
|
|
3465
|
-
*
|
|
3989
|
+
* Compute the repo-root-relative tar entries for the dependency closure. npm
|
|
3990
|
+
* needs only `.kici/node_modules`. pnpm additionally needs the root store and
|
|
3991
|
+
* the in-repo workspace siblings `.kici` resolves. yarn (classic + berry) packs
|
|
3992
|
+
* the resolved node_modules root (standalone → `.kici/node_modules`; hoisted
|
|
3993
|
+
* workspace member → root `node_modules`) plus the in-repo siblings, which it
|
|
3994
|
+
* links but does not place in the store. Berry's forced node-modules linker
|
|
3995
|
+
* gives it the same packed shape as classic.
|
|
3466
3996
|
*/
|
|
3467
3997
|
async function closureEntries(workDir, kiciDir, packageManager) {
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
return entries;
|
|
3474
|
-
}
|
|
3475
|
-
/**
|
|
3476
|
-
* Walk `.kici`'s `node_modules` (and transitively each in-repo sibling's
|
|
3477
|
-
* `node_modules`) collecting the repo-relative directories of `workspace:`
|
|
3478
|
-
* siblings — package dirs that live inside the clone but outside `.kici/` and
|
|
3479
|
-
* outside the root `node_modules/` store. Returns each dir once.
|
|
3480
|
-
*/
|
|
3481
|
-
async function collectInRepoSiblings(workDir, kiciDir) {
|
|
3482
|
-
const repoRoot = resolve(workDir);
|
|
3483
|
-
const kiciResolved = resolve(kiciDir);
|
|
3484
|
-
const rootNodeModules = resolve(join(workDir, "node_modules"));
|
|
3485
|
-
const found = /* @__PURE__ */ new Set();
|
|
3486
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3487
|
-
const queue = [join(kiciDir, "node_modules")];
|
|
3488
|
-
while (queue.length > 0) {
|
|
3489
|
-
const nmDir = queue.shift();
|
|
3490
|
-
const real = await realpath(nmDir).catch(() => null);
|
|
3491
|
-
if (!real || visited.has(real)) continue;
|
|
3492
|
-
visited.add(real);
|
|
3493
|
-
for (const target of await resolveNodeModulesLinks(nmDir)) {
|
|
3494
|
-
if (!isInside(repoRoot, target)) continue;
|
|
3495
|
-
if (isInside(kiciResolved, target) || isInside(rootNodeModules, target)) continue;
|
|
3496
|
-
const rel = relative(workDir, target);
|
|
3497
|
-
if (!found.has(rel)) {
|
|
3498
|
-
found.add(rel);
|
|
3499
|
-
queue.push(join(target, "node_modules"));
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3998
|
+
if (packageManager === PackageManager.Pnpm) {
|
|
3999
|
+
const entries = [relative(workDir, join(kiciDir, "node_modules"))];
|
|
4000
|
+
if (existsSync(join(workDir, "node_modules", ".pnpm"))) entries.push(join("node_modules", ".pnpm"));
|
|
4001
|
+
for (const sibling of await collectInRepoSiblings(workDir, kiciDir)) entries.push(sibling);
|
|
4002
|
+
return entries;
|
|
3502
4003
|
}
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
for (const entry of await readdir(nmDir).catch(() => [])) {
|
|
3509
|
-
if (entry.startsWith(".")) continue;
|
|
3510
|
-
const entryPath = join(nmDir, entry);
|
|
3511
|
-
if (entry.startsWith("@")) {
|
|
3512
|
-
for (const scoped of await readdir(entryPath).catch(() => [])) {
|
|
3513
|
-
const target = await resolveIfSymlink(join(entryPath, scoped));
|
|
3514
|
-
if (target) targets.push(target);
|
|
3515
|
-
}
|
|
3516
|
-
continue;
|
|
3517
|
-
}
|
|
3518
|
-
const target = await resolveIfSymlink(entryPath);
|
|
3519
|
-
if (target) targets.push(target);
|
|
4004
|
+
if (packageManager === PackageManager.Yarn) {
|
|
4005
|
+
const nmRoot = resolveYarnNodeModulesRoot(workDir, kiciDir);
|
|
4006
|
+
const entries = [relative(workDir, nmRoot)];
|
|
4007
|
+
for (const sibling of await collectInRepoSiblings(workDir, kiciDir, nmRoot)) entries.push(sibling);
|
|
4008
|
+
return entries;
|
|
3520
4009
|
}
|
|
3521
|
-
return
|
|
3522
|
-
}
|
|
3523
|
-
/** Return the real path of `p` if it is a symlink, else null. */
|
|
3524
|
-
async function resolveIfSymlink(p) {
|
|
3525
|
-
try {
|
|
3526
|
-
if (!(await lstat(p)).isSymbolicLink()) return null;
|
|
3527
|
-
return await realpath(p);
|
|
3528
|
-
} catch {
|
|
3529
|
-
return null;
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
/** Whether `target` is `root` itself or a path inside it. */
|
|
3533
|
-
function isInside(root, target) {
|
|
3534
|
-
const rel = relative(root, target);
|
|
3535
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith(`..${sep}`) && !isAbsoluteRel(rel);
|
|
3536
|
-
}
|
|
3537
|
-
function isAbsoluteRel(rel) {
|
|
3538
|
-
return rel.length > 1 && rel[1] === ":";
|
|
4010
|
+
return [relative(workDir, join(kiciDir, "node_modules"))];
|
|
3539
4011
|
}
|
|
3540
4012
|
var logger$5;
|
|
3541
4013
|
var init_dep_packer = __esmMin((() => {
|
|
4014
|
+
init_workspace_siblings();
|
|
3542
4015
|
logger$5 = createLogger({ prefix: "dep-packer" });
|
|
3543
4016
|
}));
|
|
3544
4017
|
//#endregion
|
|
@@ -3682,6 +4155,8 @@ var init_secret_encryption = __esmMin((() => {
|
|
|
3682
4155
|
function buildRequest(dispatch, workDir) {
|
|
3683
4156
|
const jobConfig = dispatch.jobConfig;
|
|
3684
4157
|
return {
|
|
4158
|
+
runId: dispatch.runId,
|
|
4159
|
+
jobId: dispatch.jobId,
|
|
3685
4160
|
workDir,
|
|
3686
4161
|
repoUrl: dispatch.repoUrl,
|
|
3687
4162
|
ref: dispatch.ref,
|
|
@@ -3694,8 +4169,11 @@ function buildRequest(dispatch, workDir) {
|
|
|
3694
4169
|
depsUrl: dispatch.depsUrl,
|
|
3695
4170
|
depsHash: dispatch.depsHash,
|
|
3696
4171
|
workflowName: jobConfig.workflowName ?? "",
|
|
3697
|
-
jobName: jobConfig.name ?? "",
|
|
4172
|
+
jobName: jobConfig.baseJobName ?? jobConfig.name ?? "",
|
|
3698
4173
|
runsOn: jobConfig.runsOn ?? "",
|
|
4174
|
+
matrixValues: jobConfig.matrixValues,
|
|
4175
|
+
host: jobConfig.host,
|
|
4176
|
+
agent: jobConfig.agent,
|
|
3699
4177
|
secrets: dispatch.secrets,
|
|
3700
4178
|
namespacedSecrets: dispatch.namespacedSecrets,
|
|
3701
4179
|
sourceFile: jobConfig.source?.file,
|
|
@@ -3962,6 +4440,24 @@ function relayCacheRequest$1(msg, ctx) {
|
|
|
3962
4440
|
error: toErrorMessage(err)
|
|
3963
4441
|
}));
|
|
3964
4442
|
}
|
|
4443
|
+
/** Relay `provenance.request` and pipe the orchestrator response (or an error
|
|
4444
|
+
* response, or a "not configured" response when the callback isn't wired) back
|
|
4445
|
+
* into the sandbox runner. */
|
|
4446
|
+
function relayProvenanceRequest$1(msg, ctx) {
|
|
4447
|
+
if (!ctx.execOptions.onProvenanceRequest) {
|
|
4448
|
+
safeSendToChild(ctx.child, {
|
|
4449
|
+
type: "provenance.response",
|
|
4450
|
+
requestId: msg.requestId,
|
|
4451
|
+
error: "Provenance not available in this agent configuration"
|
|
4452
|
+
});
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
ctx.execOptions.onProvenanceRequest(msg).then((response) => safeSendToChild(ctx.child, response), (err) => safeSendToChild(ctx.child, {
|
|
4456
|
+
type: "provenance.response",
|
|
4457
|
+
requestId: msg.requestId,
|
|
4458
|
+
error: toErrorMessage(err)
|
|
4459
|
+
}));
|
|
4460
|
+
}
|
|
3965
4461
|
/** Relay `approval.request` and pipe the orchestrator's resolution (or a
|
|
3966
4462
|
* fail-closed reject when the callback isn't wired or the relay throws) back
|
|
3967
4463
|
* into the sandbox runner. */
|
|
@@ -4052,6 +4548,9 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
4052
4548
|
case "cache.request":
|
|
4053
4549
|
relayCacheRequest$1(msg, ctx);
|
|
4054
4550
|
return;
|
|
4551
|
+
case "provenance.request":
|
|
4552
|
+
relayProvenanceRequest$1(msg, ctx);
|
|
4553
|
+
return;
|
|
4055
4554
|
case "approval.request":
|
|
4056
4555
|
relayApprovalRequest$1(msg, ctx);
|
|
4057
4556
|
return;
|
|
@@ -4476,6 +4975,32 @@ function relayCacheRequest(stream, options, cacheMsg) {
|
|
|
4476
4975
|
}));
|
|
4477
4976
|
}
|
|
4478
4977
|
/**
|
|
4978
|
+
* Relay provenance.request from the container runner to the orchestrator via
|
|
4979
|
+
* options.onProvenanceRequest, then write the response back through `stream`.
|
|
4980
|
+
* If the agent doesn't expose a provenance relay, write a structured error so
|
|
4981
|
+
* the runner doesn't hang.
|
|
4982
|
+
*/
|
|
4983
|
+
function relayProvenanceRequest(stream, options, provMsg) {
|
|
4984
|
+
const writeResponse = (response) => {
|
|
4985
|
+
try {
|
|
4986
|
+
stream.write(JSON.stringify(response) + "\n");
|
|
4987
|
+
} catch {}
|
|
4988
|
+
};
|
|
4989
|
+
if (!options.onProvenanceRequest) {
|
|
4990
|
+
writeResponse({
|
|
4991
|
+
type: "provenance.response",
|
|
4992
|
+
requestId: provMsg.requestId,
|
|
4993
|
+
error: "Provenance not available in this agent configuration"
|
|
4994
|
+
});
|
|
4995
|
+
return;
|
|
4996
|
+
}
|
|
4997
|
+
options.onProvenanceRequest(provMsg).then((response) => writeResponse(response), (err) => writeResponse({
|
|
4998
|
+
type: "provenance.response",
|
|
4999
|
+
requestId: provMsg.requestId,
|
|
5000
|
+
error: toErrorMessage(err)
|
|
5001
|
+
}));
|
|
5002
|
+
}
|
|
5003
|
+
/**
|
|
4479
5004
|
* Relay approval.request from the container runner to the orchestrator via
|
|
4480
5005
|
* options.onApprovalRequest, then write the resolution back through `stream`.
|
|
4481
5006
|
* If the agent doesn't expose an approval relay (or it throws), write a
|
|
@@ -4758,6 +5283,9 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
4758
5283
|
case "cache.request":
|
|
4759
5284
|
relayCacheRequest(stream, options, msg);
|
|
4760
5285
|
return false;
|
|
5286
|
+
case "provenance.request":
|
|
5287
|
+
relayProvenanceRequest(stream, options, msg);
|
|
5288
|
+
return false;
|
|
4761
5289
|
case "approval.request":
|
|
4762
5290
|
relayApprovalRequest(stream, options, msg);
|
|
4763
5291
|
return false;
|
|
@@ -4857,7 +5385,7 @@ var job_runner_exports = /* @__PURE__ */ __exportAll({ JobRunner: () => JobRunne
|
|
|
4857
5385
|
*/
|
|
4858
5386
|
async function fileExists(p) {
|
|
4859
5387
|
try {
|
|
4860
|
-
await fs.access(p);
|
|
5388
|
+
await fs$1.access(p);
|
|
4861
5389
|
return true;
|
|
4862
5390
|
} catch {
|
|
4863
5391
|
return false;
|
|
@@ -4899,6 +5427,14 @@ function determineExecutionMode(jobConfig, agentConfig) {
|
|
|
4899
5427
|
if (agentConfig.scalerManaged) return "firecracker";
|
|
4900
5428
|
return "bare-metal";
|
|
4901
5429
|
}
|
|
5430
|
+
/**
|
|
5431
|
+
* Build the result-aware `ctx.needs` for a dynamic eval from its frozen upstream
|
|
5432
|
+
* snapshot. Returns undefined for an event-only generator (no snapshot).
|
|
5433
|
+
*/
|
|
5434
|
+
function buildEvalNeedsContext(config) {
|
|
5435
|
+
if (!config.resultAware || !config.upstreamSnapshot) return void 0;
|
|
5436
|
+
return buildNeedsContext(config.upstreamSnapshot, config.declaredNeeds ?? []);
|
|
5437
|
+
}
|
|
4902
5438
|
var logger$2, JobRunner$1;
|
|
4903
5439
|
var init_job_runner = __esmMin((() => {
|
|
4904
5440
|
init_git_clone();
|
|
@@ -4931,6 +5467,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4931
5467
|
_sendConcurrencyReport;
|
|
4932
5468
|
_sendApiRequest;
|
|
4933
5469
|
_requestUserCache;
|
|
5470
|
+
_relayProvenance;
|
|
4934
5471
|
_sendStepApproval;
|
|
4935
5472
|
/** Tracks running jobs for concurrency and cancellation */
|
|
4936
5473
|
activeJobs = /* @__PURE__ */ new Map();
|
|
@@ -4950,6 +5487,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
4950
5487
|
this._sendConcurrencyReport = deps.sendConcurrencyReport;
|
|
4951
5488
|
this._sendApiRequest = deps.sendApiRequest;
|
|
4952
5489
|
this._requestUserCache = deps.requestUserCache;
|
|
5490
|
+
this._relayProvenance = deps.relayProvenance;
|
|
4953
5491
|
this._sendStepApproval = deps.sendStepApproval;
|
|
4954
5492
|
}
|
|
4955
5493
|
/**
|
|
@@ -4961,11 +5499,11 @@ var init_job_runner = __esmMin((() => {
|
|
|
4961
5499
|
async execute(dispatch) {
|
|
4962
5500
|
const { runId: _runId, jobId, jobConfig: _jobConfig } = dispatch;
|
|
4963
5501
|
const abortController = new AbortController();
|
|
4964
|
-
const workDir = await fs.mkdtemp(join(tmpdir(), "kici-"));
|
|
5502
|
+
const workDir = await fs$1.mkdtemp(join(tmpdir(), "kici-"));
|
|
4965
5503
|
const completionPromise = this.runJob(dispatch, workDir, abortController).finally(async () => {
|
|
4966
5504
|
this.activeJobs.delete(jobId);
|
|
4967
5505
|
this.activeSandbox = null;
|
|
4968
|
-
await fs.rm(workDir, {
|
|
5506
|
+
await fs$1.rm(workDir, {
|
|
4969
5507
|
recursive: true,
|
|
4970
5508
|
force: true
|
|
4971
5509
|
}).catch(() => {});
|
|
@@ -5190,6 +5728,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
5190
5728
|
},
|
|
5191
5729
|
onApiRequest: this._sendApiRequest ? async (method, params) => this._sendApiRequest(method, params) : void 0,
|
|
5192
5730
|
onCacheRequest: this._requestUserCache ? async (request) => this._requestUserCache(jobId, request) : void 0,
|
|
5731
|
+
onProvenanceRequest: this._relayProvenance ? async (request) => this._relayProvenance(jobId, request) : void 0,
|
|
5193
5732
|
onApprovalRequest: this._sendStepApproval ? async (request) => this._sendStepApproval(dispatch.runId, dispatch.jobId, request) : void 0,
|
|
5194
5733
|
onSecretMount: (event) => {
|
|
5195
5734
|
this.emitRunEvent(runId, "step.secret_mount", {
|
|
@@ -5509,11 +6048,12 @@ var init_job_runner = __esmMin((() => {
|
|
|
5509
6048
|
const initResult = await runCaptured(initSink, async () => {
|
|
5510
6049
|
const { module } = await loadWorkflowSource(workDir, config.source, config.contentHash, config.resolvedHashFiles);
|
|
5511
6050
|
const workflow = extractWorkflow(module, config.workflowName);
|
|
5512
|
-
initLog(`Evaluating dynamic fields for job '${config.targetJobName}' (env=${config.dynamicEnv} environment=${config.dynamicEnvironment} concurrencyGroup=${config.dynamicConcurrencyGroup})`);
|
|
6051
|
+
initLog(`Evaluating dynamic fields for job '${config.targetJobName}' (env=${config.dynamicEnv} environment=${config.dynamicEnvironment} concurrencyGroup=${config.dynamicConcurrencyGroup} matrix=${config.dynamicMatrix ?? false})`);
|
|
5513
6052
|
return evaluateDynamicFields(workflow, config.targetJobName, config.event, {
|
|
5514
6053
|
dynamicEnvironment: config.dynamicEnvironment,
|
|
5515
6054
|
dynamicEnv: config.dynamicEnv,
|
|
5516
|
-
dynamicConcurrencyGroup: config.dynamicConcurrencyGroup
|
|
6055
|
+
dynamicConcurrencyGroup: config.dynamicConcurrencyGroup,
|
|
6056
|
+
dynamicMatrix: config.dynamicMatrix ?? false
|
|
5517
6057
|
}, config.timeoutMs);
|
|
5518
6058
|
});
|
|
5519
6059
|
logger$2.info("Init job completed successfully", {
|
|
@@ -5642,11 +6182,13 @@ var init_job_runner = __esmMin((() => {
|
|
|
5642
6182
|
const { extractDynamicJobFn } = await Promise.resolve().then(() => (init_workflow_loader(), workflow_loader_exports));
|
|
5643
6183
|
const dynamicFn = extractDynamicJobFn(extractWorkflow(module, config.workflowName), config.source.index);
|
|
5644
6184
|
evalLog(`Evaluating DynamicJobFn (index ${config.source.index}, timeout ${timeoutMs}ms)`);
|
|
6185
|
+
const needs = buildEvalNeedsContext(config);
|
|
5645
6186
|
const context = {
|
|
5646
6187
|
$: scopedDollar,
|
|
5647
6188
|
ctx: {
|
|
5648
6189
|
workflow: { name: config.workflowName },
|
|
5649
|
-
event: config.event
|
|
6190
|
+
event: config.event,
|
|
6191
|
+
...needs && { needs }
|
|
5650
6192
|
},
|
|
5651
6193
|
log: dynamicJobLogger,
|
|
5652
6194
|
env: process.env,
|
|
@@ -5863,14 +6405,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
5863
6405
|
*/
|
|
5864
6406
|
init_console_capture();
|
|
5865
6407
|
init_npm_resolver();
|
|
5866
|
-
const AGENT_VERSION = "0.1.
|
|
5867
|
-
const BUILD_COMMIT = "
|
|
5868
|
-
const SDK_VERSION = "0.1.
|
|
5869
|
-
const SDK_BUNDLE_HASH = "
|
|
5870
|
-
const SHARED_VERSION = "0.1.
|
|
5871
|
-
const SHARED_BUNDLE_HASH = "
|
|
5872
|
-
const ENGINE_VERSION = "0.1.
|
|
5873
|
-
const ENGINE_BUNDLE_HASH = "
|
|
6408
|
+
const AGENT_VERSION = "0.1.18";
|
|
6409
|
+
const BUILD_COMMIT = "d8cff38bb";
|
|
6410
|
+
const SDK_VERSION = "0.1.18";
|
|
6411
|
+
const SDK_BUNDLE_HASH = "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
6412
|
+
const SHARED_VERSION = "0.1.18";
|
|
6413
|
+
const SHARED_BUNDLE_HASH = "5f2c220f24d166b0f13d0620a2e80d19689ac8b683a12154daef44e938fd46a7";
|
|
6414
|
+
const ENGINE_VERSION = "0.1.18";
|
|
6415
|
+
const ENGINE_BUNDLE_HASH = "79ce14640d1798eaaa7cb3aa6c4bc325da3bff1e9f4716936e19614eabb1d858";
|
|
5874
6416
|
initTelemetry({
|
|
5875
6417
|
serviceName: "kici-agent",
|
|
5876
6418
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
@@ -5951,6 +6493,7 @@ await guardStartup(logger$1, async () => {
|
|
|
5951
6493
|
sendConcurrencyReport: (runId, jobId, group) => client.sendConcurrencyReport(runId, jobId, group),
|
|
5952
6494
|
sendApiRequest: (method, params) => client.sendApiRequest(method, params ?? {}),
|
|
5953
6495
|
requestUserCache: (jobId, request) => client.requestUserCache(jobId, request),
|
|
6496
|
+
relayProvenance: (jobId, request) => client.relayProvenance(jobId, request),
|
|
5954
6497
|
sendStepApproval: (runId, jobId, request) => client.sendStepApproval(runId, jobId, request)
|
|
5955
6498
|
});
|
|
5956
6499
|
/** Build and send an agent.status message with dynamic OS metadata. */
|