@kici-dev/agent 0.1.17 → 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 +9 -4
- package/dist/execution/dep-packer.d.ts +8 -5
- package/dist/execution/sandbox/ipc-protocol.d.ts +23 -0
- package/dist/execution/validate-kici-deps.d.ts +7 -4
- package/dist/execution/workflow-loader.d.ts +5 -1
- package/dist/execution/yarnrc-berry-config.d.ts +23 -0
- package/dist/index.js +217 -28
- package/dist/server.js +275 -79
- package/dist/workflow-runner.js +225 -34
- package/package.json +13 -12
- package/sbom.spdx.json +2551 -6049
package/dist/workflow-runner.js
CHANGED
|
@@ -24,7 +24,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
24
24
|
import { c, x } from "tar";
|
|
25
25
|
import { execFile } from "node:child_process";
|
|
26
26
|
import { promisify } from "node:util";
|
|
27
|
-
import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, detectPackageManagerFromManifests } from "@kici-dev/shared/package-manager";
|
|
27
|
+
import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, YarnFlavor, detectPackageManagerFromManifests, detectYarnFlavor } from "@kici-dev/shared/package-manager";
|
|
28
|
+
import { parse, stringify } from "yaml";
|
|
28
29
|
var __defProp = Object.defineProperty;
|
|
29
30
|
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
30
31
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -2175,7 +2176,7 @@ function noopResult() {
|
|
|
2175
2176
|
};
|
|
2176
2177
|
}
|
|
2177
2178
|
/** Build the synthesized env-var name for registry index `i`. */
|
|
2178
|
-
function tokenEnvName(jobIdShort, index) {
|
|
2179
|
+
function tokenEnvName$1(jobIdShort, index) {
|
|
2179
2180
|
return `KICI_NPM_TOKEN_${jobIdShort}_${index}`;
|
|
2180
2181
|
}
|
|
2181
2182
|
/** Render the agent-managed block of `.npmrc` lines. */
|
|
@@ -2184,7 +2185,7 @@ function renderAgentLines(registries, jobIdShort) {
|
|
|
2184
2185
|
const lines = [];
|
|
2185
2186
|
for (let i = 0; i < registries.length; i++) {
|
|
2186
2187
|
const reg = registries[i];
|
|
2187
|
-
const envVar = tokenEnvName(jobIdShort, i);
|
|
2188
|
+
const envVar = tokenEnvName$1(jobIdShort, i);
|
|
2188
2189
|
const authKey = reg.url.replace(/^https?:/, "");
|
|
2189
2190
|
if (reg.scope) lines.push(`${reg.scope}:registry=${reg.url}`);
|
|
2190
2191
|
else lines.push(`registry=${reg.url}`);
|
|
@@ -2216,7 +2217,7 @@ async function applyNpmRegistryConfig(args) {
|
|
|
2216
2217
|
const tokenEnv = {};
|
|
2217
2218
|
const tokensForRedaction = [];
|
|
2218
2219
|
for (let i = 0; i < registries.length; i++) {
|
|
2219
|
-
tokenEnv[tokenEnvName(args.jobIdShort, i)] = registries[i].token;
|
|
2220
|
+
tokenEnv[tokenEnvName$1(args.jobIdShort, i)] = registries[i].token;
|
|
2220
2221
|
tokensForRedaction.push(registries[i].token);
|
|
2221
2222
|
}
|
|
2222
2223
|
for (const value of Object.values(installEnvSecrets)) if (value) tokensForRedaction.push(value);
|
|
@@ -2252,6 +2253,112 @@ function redactNpmOutput(input, tokens) {
|
|
|
2252
2253
|
}
|
|
2253
2254
|
return out;
|
|
2254
2255
|
}
|
|
2256
|
+
//#endregion
|
|
2257
|
+
//#region src/execution/yarnrc-berry-config.ts
|
|
2258
|
+
/**
|
|
2259
|
+
* Apply yarn-berry registry auth + a forced `nodeLinker: node-modules` to a
|
|
2260
|
+
* workflow's `.kici/.yarnrc.yml` for the lifetime of one `yarn install`, then
|
|
2261
|
+
* restore the file on cleanup. The berry analog of `npm-registry-config.ts`:
|
|
2262
|
+
* berry reads `.yarnrc.yml` (not `.npmrc`), so the auth block uses berry's
|
|
2263
|
+
* `npmRegistryServer` / `npmScopes` / `npmAuthToken` keys with `${VAR}`
|
|
2264
|
+
* env-var interpolation. Token bytes never reach disk — each registry token is
|
|
2265
|
+
* exposed as a job-scoped env var and the on-disk value is the `${VAR}`
|
|
2266
|
+
* reference.
|
|
2267
|
+
*
|
|
2268
|
+
* `nodeLinker: node-modules` makes berry lay down a real `node_modules` tree
|
|
2269
|
+
* (no PnP `.pnp.cjs`), so the agent's packer / restore / sibling-walk /
|
|
2270
|
+
* workflow-loader work unchanged. `enableScripts: false` (when a private
|
|
2271
|
+
* registry is configured) keeps dependency lifecycle scripts from seeing the
|
|
2272
|
+
* synthesized token env vars — the same security model as npm/pnpm/classic
|
|
2273
|
+
* `--ignore-scripts`.
|
|
2274
|
+
*
|
|
2275
|
+
* Reuses the same `ApplyNpmRegistryConfigArgs` / `ApplyNpmRegistryConfigResult`
|
|
2276
|
+
* shapes as the npm overlay so `dep-installer` can pick either by flavor.
|
|
2277
|
+
*/
|
|
2278
|
+
/** Build the synthesized env-var name for registry index `i`. */
|
|
2279
|
+
function tokenEnvName(jobIdShort, index) {
|
|
2280
|
+
return `KICI_NPM_TOKEN_${jobIdShort}_${index}`;
|
|
2281
|
+
}
|
|
2282
|
+
/** Read + parse an existing `.yarnrc.yml`, or `{}` when absent/empty. */
|
|
2283
|
+
async function readOriginalYarnrc(path) {
|
|
2284
|
+
try {
|
|
2285
|
+
const raw = await readFile(path, "utf8");
|
|
2286
|
+
return {
|
|
2287
|
+
raw,
|
|
2288
|
+
doc: parse(raw) ?? {}
|
|
2289
|
+
};
|
|
2290
|
+
} catch (err) {
|
|
2291
|
+
if (err.code === "ENOENT") return {
|
|
2292
|
+
raw: null,
|
|
2293
|
+
doc: {}
|
|
2294
|
+
};
|
|
2295
|
+
throw err;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
function buildRegistryBlock(envVar, url, alwaysAuth) {
|
|
2299
|
+
return {
|
|
2300
|
+
npmRegistryServer: url,
|
|
2301
|
+
npmAuthToken: `\${${envVar}}`,
|
|
2302
|
+
...alwaysAuth ? { npmAlwaysAuth: true } : {}
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
async function applyYarnrcBerryConfig(args) {
|
|
2306
|
+
const registries = args.npmRegistries ?? [];
|
|
2307
|
+
const installEnvSecrets = args.installEnvSecrets ?? {};
|
|
2308
|
+
const hasPrivateRegistry = registries.length > 0 || Object.keys(installEnvSecrets).length > 0;
|
|
2309
|
+
const yarnrcPath = join(args.kiciDir, ".yarnrc.yml");
|
|
2310
|
+
const { raw: original, doc } = await readOriginalYarnrc(yarnrcPath);
|
|
2311
|
+
const cacheFolder = await mkdtemp(join(tmpdir(), "kici-yarn-berry-cache-"));
|
|
2312
|
+
const merged = {
|
|
2313
|
+
...doc,
|
|
2314
|
+
nodeLinker: "node-modules",
|
|
2315
|
+
enableGlobalCache: false,
|
|
2316
|
+
cacheFolder
|
|
2317
|
+
};
|
|
2318
|
+
const tokenEnv = {};
|
|
2319
|
+
const tokensForRedaction = [];
|
|
2320
|
+
if (hasPrivateRegistry) {
|
|
2321
|
+
merged.enableScripts = false;
|
|
2322
|
+
const npmScopes = { ...doc.npmScopes ?? {} };
|
|
2323
|
+
for (let i = 0; i < registries.length; i++) {
|
|
2324
|
+
const reg = registries[i];
|
|
2325
|
+
const envVar = tokenEnvName(args.jobIdShort, i);
|
|
2326
|
+
tokenEnv[envVar] = reg.token;
|
|
2327
|
+
tokensForRedaction.push(reg.token);
|
|
2328
|
+
const block = buildRegistryBlock(envVar, reg.url, reg.alwaysAuth);
|
|
2329
|
+
if (reg.scope) npmScopes[reg.scope] = block;
|
|
2330
|
+
else {
|
|
2331
|
+
merged.npmRegistryServer = reg.url;
|
|
2332
|
+
merged.npmAuthToken = block.npmAuthToken;
|
|
2333
|
+
if (reg.alwaysAuth) merged.npmAlwaysAuth = true;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
if (Object.keys(npmScopes).length > 0) merged.npmScopes = npmScopes;
|
|
2337
|
+
for (const value of Object.values(installEnvSecrets)) if (value) tokensForRedaction.push(value);
|
|
2338
|
+
}
|
|
2339
|
+
await writeFile(yarnrcPath, stringify(merged), {
|
|
2340
|
+
encoding: "utf8",
|
|
2341
|
+
mode: 384
|
|
2342
|
+
});
|
|
2343
|
+
const cleanup = async () => {
|
|
2344
|
+
try {
|
|
2345
|
+
if (original === null) await unlink(yarnrcPath).catch(() => {});
|
|
2346
|
+
else await writeFile(yarnrcPath, original, { encoding: "utf8" });
|
|
2347
|
+
} catch {}
|
|
2348
|
+
await rm(cacheFolder, {
|
|
2349
|
+
recursive: true,
|
|
2350
|
+
force: true
|
|
2351
|
+
}).catch(() => {});
|
|
2352
|
+
};
|
|
2353
|
+
return {
|
|
2354
|
+
extraEnv: {
|
|
2355
|
+
...installEnvSecrets,
|
|
2356
|
+
...tokenEnv
|
|
2357
|
+
},
|
|
2358
|
+
tokensForRedaction,
|
|
2359
|
+
cleanup
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2255
2362
|
const LOCAL_PROTOCOLS = [
|
|
2256
2363
|
"workspace:",
|
|
2257
2364
|
"file:",
|
|
@@ -2320,6 +2427,17 @@ async function fileExists$1(target) {
|
|
|
2320
2427
|
return false;
|
|
2321
2428
|
}
|
|
2322
2429
|
}
|
|
2430
|
+
/** Whether the repo-root package.json declares a non-empty `workspaces` array. */
|
|
2431
|
+
async function rootHasWorkspaces(repoRoot) {
|
|
2432
|
+
try {
|
|
2433
|
+
const ws = JSON.parse(await readFile(join(repoRoot, "package.json"), "utf-8")).workspaces;
|
|
2434
|
+
if (Array.isArray(ws)) return ws.length > 0;
|
|
2435
|
+
if (ws && typeof ws === "object" && Array.isArray(ws.packages)) return ws.packages.length > 0;
|
|
2436
|
+
return false;
|
|
2437
|
+
} catch {
|
|
2438
|
+
return false;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2323
2441
|
/** Resolve a `file:`/`link:`/`portal:` spec to an absolute path under kiciDir. */
|
|
2324
2442
|
function resolveLocalPath(kiciDir, dep) {
|
|
2325
2443
|
const rawPath = dep.spec.slice(dep.protocol.length);
|
|
@@ -2334,8 +2452,20 @@ function isInsideRepo(repoRoot, target) {
|
|
|
2334
2452
|
* Classify each local-protocol dependency for the detected package manager and
|
|
2335
2453
|
* return the ones that are unresolvable in the agent's single-clone model.
|
|
2336
2454
|
*/
|
|
2337
|
-
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
2455
|
+
async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot, yarnFlavor) {
|
|
2338
2456
|
if (packageManager === PackageManager.Npm) return [...deps];
|
|
2457
|
+
if (packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry) {
|
|
2458
|
+
const hasWorkspaces = await rootHasWorkspaces(repoRoot);
|
|
2459
|
+
const unresolvable = [];
|
|
2460
|
+
for (const dep of deps) {
|
|
2461
|
+
if (dep.protocol === "workspace:") {
|
|
2462
|
+
if (!hasWorkspaces) unresolvable.push(dep);
|
|
2463
|
+
continue;
|
|
2464
|
+
}
|
|
2465
|
+
if (!isInsideRepo(repoRoot, resolveLocalPath(kiciDir, dep))) unresolvable.push(dep);
|
|
2466
|
+
}
|
|
2467
|
+
return unresolvable;
|
|
2468
|
+
}
|
|
2339
2469
|
if (packageManager === PackageManager.Yarn) {
|
|
2340
2470
|
const unresolvable = [];
|
|
2341
2471
|
for (const dep of deps) {
|
|
@@ -2359,10 +2489,11 @@ async function findUnresolvableDeps(deps, packageManager, kiciDir, repoRoot) {
|
|
|
2359
2489
|
return unresolvable;
|
|
2360
2490
|
}
|
|
2361
2491
|
/** Build the actionable error for unresolvable local-protocol dependencies. */
|
|
2362
|
-
function formatUnresolvableDepError(offenders, packageManager) {
|
|
2492
|
+
function formatUnresolvableDepError(offenders, packageManager, yarnFlavor) {
|
|
2363
2493
|
const list = offenders.map((o) => `${o.name}: ${o.spec}`).join(", ");
|
|
2364
2494
|
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.`;
|
|
2365
|
-
if (packageManager === PackageManager.Yarn) return `These .kici/ dependencies
|
|
2495
|
+
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.`;
|
|
2496
|
+
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.)`;
|
|
2366
2497
|
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.`;
|
|
2367
2498
|
}
|
|
2368
2499
|
/**
|
|
@@ -2376,9 +2507,10 @@ async function assertResolvableDeps(args) {
|
|
|
2376
2507
|
if (!pkg) return;
|
|
2377
2508
|
const localDeps = findLocalProtocolDeps(pkg);
|
|
2378
2509
|
if (localDeps.length === 0) return;
|
|
2379
|
-
const
|
|
2510
|
+
const flavor = args.yarnFlavor ?? YarnFlavor.Classic;
|
|
2511
|
+
const offenders = await findUnresolvableDeps(localDeps, args.packageManager, args.kiciDir, args.repoRoot, flavor);
|
|
2380
2512
|
if (offenders.length === 0) return;
|
|
2381
|
-
throw new Error(formatUnresolvableDepError(offenders, args.packageManager));
|
|
2513
|
+
throw new Error(formatUnresolvableDepError(offenders, args.packageManager, flavor));
|
|
2382
2514
|
}
|
|
2383
2515
|
//#endregion
|
|
2384
2516
|
//#region src/execution/workspace-siblings.ts
|
|
@@ -2487,10 +2619,13 @@ function isAbsoluteRel(rel) {
|
|
|
2487
2619
|
* presence of `.kici/package.json` signals that deps should be installed. npm
|
|
2488
2620
|
* is the default and ships with every Node.js install; pnpm is used when the
|
|
2489
2621
|
* repo is a pnpm workspace so a `.kici/` member can resolve in-repo
|
|
2490
|
-
* `workspace:` siblings. yarn classic (v1)
|
|
2491
|
-
*
|
|
2492
|
-
*
|
|
2493
|
-
*
|
|
2622
|
+
* `workspace:` siblings. yarn is supported in both flavors: classic (v1) reads
|
|
2623
|
+
* `.kici/.npmrc` for registry auth and links version-range workspace siblings;
|
|
2624
|
+
* berry (v2+) reads a synthesized `.kici/.yarnrc.yml` for auth, runs with a
|
|
2625
|
+
* forced `nodeLinker: node-modules` (so the resulting tree matches classic/npm
|
|
2626
|
+
* and the runner's plain node resolution holds), and resolves
|
|
2627
|
+
* `workspace:`/`portal:` siblings. Either flavor links the sibling but does not
|
|
2628
|
+
* build it, so the agent builds the in-repo closure after install.
|
|
2494
2629
|
*
|
|
2495
2630
|
* Security: the install runs with an isolated per-invocation cache/store
|
|
2496
2631
|
* directory to prevent cache poisoning across build jobs — a malicious
|
|
@@ -2516,6 +2651,15 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
2516
2651
|
return await detectPackageManagerFromManifests(repoRoot) ?? await detectPackageManagerFromManifests(kiciDir) ?? PackageManager.Npm;
|
|
2517
2652
|
}
|
|
2518
2653
|
/**
|
|
2654
|
+
* Detect the yarn flavor (classic vs berry) for the cloned repo. Mirrors
|
|
2655
|
+
* `detectKiciPackageManager`: probe the repo root first, then `.kici/` for a
|
|
2656
|
+
* standalone project. Only called when the detected manager is `Yarn`.
|
|
2657
|
+
*/
|
|
2658
|
+
async function detectKiciYarnFlavor(repoRoot, kiciDir) {
|
|
2659
|
+
if (await detectYarnFlavor(repoRoot) === YarnFlavor.Berry) return YarnFlavor.Berry;
|
|
2660
|
+
return detectYarnFlavor(kiciDir);
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2519
2663
|
* Install `.kici/` dependencies inline with the repo's package manager.
|
|
2520
2664
|
*
|
|
2521
2665
|
* Falls back to this when the dep cache is unavailable or a download fails.
|
|
@@ -2534,19 +2678,28 @@ async function detectKiciPackageManager(repoRoot, kiciDir) {
|
|
|
2534
2678
|
async function installDeps(kiciDir, opts = {}) {
|
|
2535
2679
|
const repoRoot = opts.repoRoot ?? dirname(kiciDir);
|
|
2536
2680
|
const packageManager = await detectKiciPackageManager(repoRoot, kiciDir);
|
|
2681
|
+
const yarnFlavor = packageManager === PackageManager.Yarn ? await detectKiciYarnFlavor(repoRoot, kiciDir) : YarnFlavor.Classic;
|
|
2537
2682
|
logger$2.info("Installing deps inline", {
|
|
2538
2683
|
packageManager,
|
|
2684
|
+
yarnFlavor,
|
|
2539
2685
|
dir: kiciDir
|
|
2540
2686
|
});
|
|
2541
|
-
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, cwd=${kiciDir}\n`);
|
|
2687
|
+
process.stderr.write(`[dep-installer:trace] starting install: pm=${packageManager}, flavor=${yarnFlavor}, cwd=${kiciDir}\n`);
|
|
2542
2688
|
await assertResolvableDeps({
|
|
2543
2689
|
kiciDir,
|
|
2544
2690
|
repoRoot,
|
|
2545
|
-
packageManager
|
|
2691
|
+
packageManager,
|
|
2692
|
+
yarnFlavor
|
|
2546
2693
|
});
|
|
2547
2694
|
const startTime = Date.now();
|
|
2548
2695
|
const hasPrivateRegistry = (opts.npmRegistries?.length ?? 0) > 0 || (opts.installEnvSecrets ? Object.keys(opts.installEnvSecrets).length > 0 : false);
|
|
2549
|
-
const
|
|
2696
|
+
const isBerry = packageManager === PackageManager.Yarn && yarnFlavor === YarnFlavor.Berry;
|
|
2697
|
+
const registryConfig = isBerry ? await applyYarnrcBerryConfig({
|
|
2698
|
+
kiciDir,
|
|
2699
|
+
npmRegistries: opts.npmRegistries,
|
|
2700
|
+
installEnvSecrets: opts.installEnvSecrets,
|
|
2701
|
+
jobIdShort: opts.jobIdShort ?? "00000000"
|
|
2702
|
+
}) : await applyNpmRegistryConfig({
|
|
2550
2703
|
kiciDir,
|
|
2551
2704
|
npmRegistries: opts.npmRegistries,
|
|
2552
2705
|
installEnvSecrets: opts.installEnvSecrets,
|
|
@@ -2558,6 +2711,10 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
2558
2711
|
hasPrivateRegistry,
|
|
2559
2712
|
registryConfig
|
|
2560
2713
|
});
|
|
2714
|
+
else if (isBerry) await runYarnBerryInstall({
|
|
2715
|
+
kiciDir,
|
|
2716
|
+
registryConfig
|
|
2717
|
+
});
|
|
2561
2718
|
else if (packageManager === PackageManager.Yarn) await runYarnInstall({
|
|
2562
2719
|
kiciDir,
|
|
2563
2720
|
hasPrivateRegistry,
|
|
@@ -2577,7 +2734,7 @@ async function installDeps(kiciDir, opts = {}) {
|
|
|
2577
2734
|
await registryConfig.cleanup();
|
|
2578
2735
|
}
|
|
2579
2736
|
if (packageManager === PackageManager.Pnpm && await kiciHasLocalProtocolDeps(kiciDir)) await buildWorkspaceClosure(repoRoot);
|
|
2580
|
-
if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir);
|
|
2737
|
+
if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor);
|
|
2581
2738
|
const durationMs = Date.now() - startTime;
|
|
2582
2739
|
process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
|
|
2583
2740
|
logger$2.info("Deps installed inline", {
|
|
@@ -2706,6 +2863,35 @@ async function runYarnInstall(args) {
|
|
|
2706
2863
|
}).catch(() => {});
|
|
2707
2864
|
}
|
|
2708
2865
|
}
|
|
2866
|
+
/** Pure: argv for a berry `yarn install`. Cache + linker live in .yarnrc.yml. */
|
|
2867
|
+
function buildYarnBerryInstallArgs() {
|
|
2868
|
+
return ["install"];
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Run a berry `yarn install` from `.kici/`. The synthesized `.kici/.yarnrc.yml`
|
|
2872
|
+
* (applied by `applyYarnrcBerryConfig`) forces `nodeLinker: node-modules`, an
|
|
2873
|
+
* isolated `cacheFolder`, and — when a private registry is configured —
|
|
2874
|
+
* `enableScripts: false` + `npmScopes`/`npmRegistryServer` auth. corepack
|
|
2875
|
+
* provisions the repo-pinned berry version; `COREPACK_ENABLE_DOWNLOAD_PROMPT=0`
|
|
2876
|
+
* makes that non-interactive. Not `--immutable` (resolved URLs in the lockfile
|
|
2877
|
+
* may point at a different registry than the synthesized config).
|
|
2878
|
+
*/
|
|
2879
|
+
async function runYarnBerryInstall(args) {
|
|
2880
|
+
await assertYarnAvailable();
|
|
2881
|
+
const { nodeDir } = resolveNpm();
|
|
2882
|
+
const env = {
|
|
2883
|
+
...envWithNodeOnPath(args.registryConfig.extraEnv, nodeDir),
|
|
2884
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
|
|
2885
|
+
};
|
|
2886
|
+
const argv = buildYarnBerryInstallArgs();
|
|
2887
|
+
process.stderr.write(`[dep-installer:trace] running: yarn ${argv.join(" ")} (berry)\n`);
|
|
2888
|
+
await execFileAsync("yarn", argv, {
|
|
2889
|
+
cwd: args.kiciDir,
|
|
2890
|
+
env,
|
|
2891
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
2892
|
+
maxBuffer: INSTALL_MAX_BUFFER
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2709
2895
|
/** Throw an actionable error when the repo needs yarn but it is not installed. */
|
|
2710
2896
|
async function assertYarnAvailable() {
|
|
2711
2897
|
try {
|
|
@@ -2725,7 +2911,7 @@ async function assertYarnAvailable() {
|
|
|
2725
2911
|
* Deep cross-sibling build chains may build out of strict topological order —
|
|
2726
2912
|
* real `.kici` closures are shallow.
|
|
2727
2913
|
*/
|
|
2728
|
-
async function buildYarnWorkspaceClosure(repoRoot, kiciDir) {
|
|
2914
|
+
async function buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor) {
|
|
2729
2915
|
const siblings = await collectInRepoSiblings(repoRoot, kiciDir, resolveYarnNodeModulesRoot(repoRoot, kiciDir));
|
|
2730
2916
|
if (siblings.length === 0) return;
|
|
2731
2917
|
const { nodeDir } = resolveNpm();
|
|
@@ -2733,15 +2919,16 @@ async function buildYarnWorkspaceClosure(repoRoot, kiciDir) {
|
|
|
2733
2919
|
for (const rel of [...siblings].reverse()) {
|
|
2734
2920
|
const sibDir = join(repoRoot, rel);
|
|
2735
2921
|
if (!await siblingHasBuildScript(sibDir)) continue;
|
|
2736
|
-
|
|
2922
|
+
const [argv, cwd] = yarnFlavor === YarnFlavor.Berry ? [["run", "build"], sibDir] : [[
|
|
2923
|
+
"--cwd",
|
|
2924
|
+
sibDir,
|
|
2925
|
+
"run",
|
|
2926
|
+
"build"
|
|
2927
|
+
], repoRoot];
|
|
2928
|
+
process.stderr.write(`[dep-installer:trace] building yarn sibling (${yarnFlavor}): yarn ${argv.join(" ")} @ ${cwd}\n`);
|
|
2737
2929
|
try {
|
|
2738
|
-
await execFileAsync("yarn",
|
|
2739
|
-
|
|
2740
|
-
sibDir,
|
|
2741
|
-
"run",
|
|
2742
|
-
"build"
|
|
2743
|
-
], {
|
|
2744
|
-
cwd: repoRoot,
|
|
2930
|
+
await execFileAsync("yarn", argv, {
|
|
2931
|
+
cwd,
|
|
2745
2932
|
env,
|
|
2746
2933
|
timeout: INSTALL_TIMEOUT_MS,
|
|
2747
2934
|
maxBuffer: INSTALL_MAX_BUFFER
|
|
@@ -2826,8 +3013,8 @@ function logSubprocessStreams(e, tokens) {
|
|
|
2826
3013
|
* no Rolldown step at runtime. `@kici-dev/sdk` and host-repo deps resolve via
|
|
2827
3014
|
* Node's normal ESM lookup against `.kici/node_modules/`.
|
|
2828
3015
|
*/
|
|
2829
|
-
const AGENT_SDK_VERSION = "0.1.
|
|
2830
|
-
const AGENT_SDK_BUNDLE_HASH = "
|
|
3016
|
+
const AGENT_SDK_VERSION = "0.1.18";
|
|
3017
|
+
const AGENT_SDK_BUNDLE_HASH = "8308089347c304e41b457d3867b17bbff11d6b5cd9706b6823e7abdbd849f33f";
|
|
2831
3018
|
/**
|
|
2832
3019
|
* Register the `@kici-dev/core/ts-loader-hook` oxc-transform ESM loader hook so
|
|
2833
3020
|
* subsequent dynamic `import()` calls for `.ts` / `.tsx` files transform on the
|
|
@@ -2950,18 +3137,20 @@ function extractSteps(workflow, jobName) {
|
|
|
2950
3137
|
* A sibling mismatch logs a warning; a missing target job throws a clear
|
|
2951
3138
|
* determinism error.
|
|
2952
3139
|
*/
|
|
2953
|
-
async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event, env, apiTransport, expectedJobNames) {
|
|
3140
|
+
async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event, env, apiTransport, expectedJobNames, upstreamSnapshot, declaredNeeds) {
|
|
2954
3141
|
const dynamicFn = extractDynamicJobFn(workflow, dynamicIndex);
|
|
2955
3142
|
const { $ } = await import("zx");
|
|
2956
3143
|
const { createLogger } = await import("@kici-dev/shared");
|
|
2957
|
-
const { buildKiciApi } = await import("@kici-dev/sdk");
|
|
3144
|
+
const { buildKiciApi, buildNeedsContext } = await import("@kici-dev/sdk");
|
|
2958
3145
|
const log = createLogger({ prefix: `dynamic-job-fn:${workflow.name}` });
|
|
2959
3146
|
const kici = buildKiciApi(apiTransport ?? (() => Promise.reject(/* @__PURE__ */ new Error("Agent API not available during re-evaluation"))));
|
|
3147
|
+
const needs = upstreamSnapshot ? buildNeedsContext(upstreamSnapshot, declaredNeeds ?? []) : void 0;
|
|
2960
3148
|
const generatedJobs = await dynamicFn({
|
|
2961
3149
|
$,
|
|
2962
3150
|
ctx: {
|
|
2963
3151
|
workflow: { name: workflow.name },
|
|
2964
|
-
event
|
|
3152
|
+
event,
|
|
3153
|
+
...needs && { needs }
|
|
2965
3154
|
},
|
|
2966
3155
|
log,
|
|
2967
3156
|
env,
|
|
@@ -3187,7 +3376,7 @@ async function applyOverlay(config) {
|
|
|
3187
3376
|
*/
|
|
3188
3377
|
init_download();
|
|
3189
3378
|
init_dep_restore();
|
|
3190
|
-
const AGENT_VERSION = "0.1.
|
|
3379
|
+
const AGENT_VERSION = "0.1.18";
|
|
3191
3380
|
process.on("uncaughtException", (err) => {
|
|
3192
3381
|
process.stderr.write(`[workflow-runner] UNCAUGHT EXCEPTION: ${err.message}\n`);
|
|
3193
3382
|
if (err.stack) process.stderr.write(`[workflow-runner] Stack: ${err.stack}\n`);
|
|
@@ -4034,7 +4223,9 @@ function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedS
|
|
|
4034
4223
|
attestProvenance: buildAttestProvenanceFn(request, workDir, (o) => kici.oidc.token(o)),
|
|
4035
4224
|
...rawPayload && { rawPayload },
|
|
4036
4225
|
...request.provider && { provider: request.provider },
|
|
4037
|
-
...request.matrixValues && { matrix: request.matrixValues }
|
|
4226
|
+
...request.matrixValues && { matrix: request.matrixValues },
|
|
4227
|
+
...request.host && { host: request.host },
|
|
4228
|
+
...request.agent && { agent: request.agent }
|
|
4038
4229
|
};
|
|
4039
4230
|
}
|
|
4040
4231
|
/** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
|
|
@@ -4585,7 +4776,7 @@ async function extractAndNormalizeSteps(workflow, request, apiTransport) {
|
|
|
4585
4776
|
let rawSteps;
|
|
4586
4777
|
let driftDroppedJobs = [];
|
|
4587
4778
|
if (request.dynamicSource) {
|
|
4588
|
-
const dynamicResult = await extractStepsFromDynamicJob(workflow, request.dynamicSource.index, request.jobName, request.dynamicSource.event, process.env, apiTransport, request.dynamicSource.expectedJobNames);
|
|
4779
|
+
const dynamicResult = await extractStepsFromDynamicJob(workflow, request.dynamicSource.index, request.jobName, request.dynamicSource.event, process.env, apiTransport, request.dynamicSource.expectedJobNames, request.dynamicSource.upstreamSnapshot, request.dynamicSource.declaredNeeds);
|
|
4589
4780
|
rawSteps = dynamicResult.steps;
|
|
4590
4781
|
driftDroppedJobs = dynamicResult.droppedJobs;
|
|
4591
4782
|
if (driftDroppedJobs.length > 0) trace(`Determinism drift: ${driftDroppedJobs.length} job(s) dropped: ${driftDroppedJobs.join(", ")}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Customer-deployable agent for the KiCI CI/CD stack. Connects to an orchestrator, clones the workflow repo, executes steps, and streams logs back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -53,20 +53,21 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@hono/node-server": "^2.0.
|
|
57
|
-
"archiver": "^
|
|
56
|
+
"@hono/node-server": "^2.0.4",
|
|
57
|
+
"archiver": "^8.0.0",
|
|
58
58
|
"dockerode": "^5.0.0",
|
|
59
|
-
"hono": "^4.12.
|
|
59
|
+
"hono": "^4.12.25",
|
|
60
60
|
"jose": "^6.1.0",
|
|
61
|
-
"tar": "^7.5.
|
|
61
|
+
"tar": "^7.5.16",
|
|
62
62
|
"winston": "^3.19.0",
|
|
63
|
-
"ws": "^8.
|
|
64
|
-
"
|
|
63
|
+
"ws": "^8.21.0",
|
|
64
|
+
"yaml": "^2.9.0",
|
|
65
|
+
"zod": "^4.4.3",
|
|
65
66
|
"zx": "^8.8.5",
|
|
66
|
-
"@kici-dev/core": "0.1.
|
|
67
|
-
"@kici-dev/sdk": "0.1.
|
|
68
|
-
"@kici-dev/
|
|
69
|
-
"@kici-dev/
|
|
67
|
+
"@kici-dev/core": "0.1.18",
|
|
68
|
+
"@kici-dev/sdk": "0.1.18",
|
|
69
|
+
"@kici-dev/engine": "0.1.18",
|
|
70
|
+
"@kici-dev/shared": "0.1.18"
|
|
70
71
|
},
|
|
71
72
|
"kici": {
|
|
72
73
|
"metrics": {
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|
|
78
|
-
"@types/archiver": "^
|
|
79
|
+
"@types/archiver": "^8.0.0",
|
|
79
80
|
"@types/dockerode": "^4.0.1"
|
|
80
81
|
},
|
|
81
82
|
"scripts": {
|